Introduction

In this notebook we demonstrate how to use Milo to detect abherrant cell states in diseased tissues, using a dataset of hepatic non-parenchymal cells isolated from 5 healthy and 5 cirrhotic human livers. Ramachandran et al. 2019 (GEO accessiion: GSE136103).

Installation

Added by DRT 2021-12-03

Updated by DRT 2023-11-29

source("getReqdPkgs.r")

pkgs <- c("SingleCellExperiment","scater","scran","Seurat","miloR",
  "tidyverse","patchwork","igraph","ggplot2",
  "ggrastr","msigdbr","clusterProfiler",
  "RColorBrewer","cowplot","devtools")

extra_pkgs <- c("irlba","Matrix") # NOTE these must be installed from source to make code work; see next code block

invisible(getReqdPkgs(pkgs))
'getOption("repos")' replaces Bioconductor standard repositories, see 'help("repositories", package = "BiocManager")' for
details.
Replacement repositories:
    CRAN: https://cran.rstudio.com/
All required CRAN and BioC packages have previously been installed

NOTE

As of 2023-11-29, the runPCA function in scater breaks due to change in Matrix library; must downgrade to Matrix 1.6.1 Downloaded v 1.6.1 from https://cran.r-project.org/src/contrib/Archive/Matrix/

installing from source (compiling on your computer, rather than installing from a binary) may also be needed. On a Mac, this requires gfortran to be installed. See here and link to download gfortran 12.2

See this link: https://github.com/bwlewis/irlba/issues/70

# install.packages("irlba", type = "source")
# install.packages("Matrix", type = "source") # , version="1.6.1"
library(scater)
Loading required package: SingleCellExperiment
Loading required package: SummarizedExperiment
Loading required package: MatrixGenerics
Loading required package: matrixStats

Attaching package: ‘MatrixGenerics’

The following objects are masked from ‘package:matrixStats’:

    colAlls, colAnyNAs, colAnys, colAvgsPerRowSet, colCollapse, colCounts, colCummaxs, colCummins,
    colCumprods, colCumsums, colDiffs, colIQRDiffs, colIQRs, colLogSumExps, colMadDiffs, colMads, colMaxs,
    colMeans2, colMedians, colMins, colOrderStats, colProds, colQuantiles, colRanges, colRanks, colSdDiffs,
    colSds, colSums2, colTabulates, colVarDiffs, colVars, colWeightedMads, colWeightedMeans,
    colWeightedMedians, colWeightedSds, colWeightedVars, rowAlls, rowAnyNAs, rowAnys, rowAvgsPerColSet,
    rowCollapse, rowCounts, rowCummaxs, rowCummins, rowCumprods, rowCumsums, rowDiffs, rowIQRDiffs, rowIQRs,
    rowLogSumExps, rowMadDiffs, rowMads, rowMaxs, rowMeans2, rowMedians, rowMins, rowOrderStats, rowProds,
    rowQuantiles, rowRanges, rowRanks, rowSdDiffs, rowSds, rowSums2, rowTabulates, rowVarDiffs, rowVars,
    rowWeightedMads, rowWeightedMeans, rowWeightedMedians, rowWeightedSds, rowWeightedVars

Loading required package: GenomicRanges
Loading required package: stats4
Loading required package: BiocGenerics

Attaching package: ‘BiocGenerics’

The following objects are masked from ‘package:stats’:

    IQR, mad, sd, var, xtabs

The following objects are masked from ‘package:base’:

    anyDuplicated, aperm, append, as.data.frame, basename, cbind, colnames, dirname, do.call, duplicated,
    eval, evalq, Filter, Find, get, grep, grepl, intersect, is.unsorted, lapply, Map, mapply, match, mget,
    order, paste, pmax, pmax.int, pmin, pmin.int, Position, rank, rbind, Reduce, rownames, sapply, setdiff,
    sort, table, tapply, union, unique, unsplit, which.max, which.min

Loading required package: S4Vectors

Attaching package: ‘S4Vectors’

The following object is masked from ‘package:utils’:

    findMatches

The following objects are masked from ‘package:base’:

    expand.grid, I, unname

Loading required package: IRanges
Loading required package: GenomeInfoDb
Loading required package: Biobase
Welcome to Bioconductor

    Vignettes contain introductory material; view with 'browseVignettes()'. To cite Bioconductor, see
    'citation("Biobase")', and for packages 'citation("pkgname")'.


Attaching package: ‘Biobase’

The following object is masked from ‘package:MatrixGenerics’:

    rowMedians

The following objects are masked from ‘package:matrixStats’:

    anyMissing, rowMedians

Loading required package: scuttle
Loading required package: ggplot2
library(scran)
library(miloR)
Loading required package: edgeR
Loading required package: limma

Attaching package: ‘limma’

The following object is masked from ‘package:scater’:

    plotMDS

The following object is masked from ‘package:BiocGenerics’:

    plotMA


Attaching package: ‘edgeR’

The following object is masked from ‘package:SingleCellExperiment’:

    cpm
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────────────────────────────────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.4
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ lubridate 1.9.3     ✔ tibble    3.2.1
✔ purrr     1.0.2     ✔ tidyr     1.3.0── Conflicts ────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ lubridate::%within%() masks IRanges::%within%()
✖ dplyr::collapse()     masks IRanges::collapse()
✖ dplyr::combine()      masks Biobase::combine(), BiocGenerics::combine()
✖ dplyr::count()        masks matrixStats::count()
✖ dplyr::desc()         masks IRanges::desc()
✖ tidyr::expand()       masks S4Vectors::expand()
✖ dplyr::filter()       masks stats::filter()
✖ dplyr::first()        masks S4Vectors::first()
✖ dplyr::lag()          masks stats::lag()
✖ ggplot2::Position()   masks BiocGenerics::Position(), base::Position()
✖ purrr::reduce()       masks GenomicRanges::reduce(), IRanges::reduce()
✖ dplyr::rename()       masks S4Vectors::rename()
✖ lubridate::second()   masks S4Vectors::second()
✖ lubridate::second<-() masks S4Vectors::second<-()
✖ dplyr::slice()        masks IRanges::slice()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(patchwork)
library(igraph)

Attaching package: ‘igraph’

The following objects are masked from ‘package:lubridate’:

    %--%, union

The following objects are masked from ‘package:dplyr’:

    as_data_frame, groups, union

The following objects are masked from ‘package:purrr’:

    compose, simplify

The following object is masked from ‘package:tidyr’:

    crossing

The following object is masked from ‘package:tibble’:

    as_data_frame

The following object is masked from ‘package:miloR’:

    graph

The following object is masked from ‘package:scater’:

    normalize

The following object is masked from ‘package:GenomicRanges’:

    union

The following object is masked from ‘package:IRanges’:

    union

The following object is masked from ‘package:S4Vectors’:

    union

The following objects are masked from ‘package:BiocGenerics’:

    normalize, path, union

The following objects are masked from ‘package:stats’:

    decompose, spectrum

The following object is masked from ‘package:base’:

    union
library(ggplot2)
library(ggrastr)
library(msigdbr)
library(cowplot)

Attaching package: ‘cowplot’

The following object is masked from ‘package:patchwork’:

    align_plots

The following object is masked from ‘package:lubridate’:

    stamp
library(Matrix)

Attaching package: ‘Matrix’

The following objects are masked from ‘package:tidyr’:

    expand, pack, unpack

The following object is masked from ‘package:S4Vectors’:

    expand
library(irlba)
library(RColorBrewer)
library(SingleCellExperiment)

Define some global variables

Location to save output files and whether to overwrite output files.

OUTPUT_DIR <- "~/dropbox-vu/temp/milo_output"
OVERWRITE <- FALSE

Load data

We downloaded the dataset and annotations stored in Seurat object from here, as indicated by the authors.

# load("/nfs/team205/ed6/data/Ramachandran2019_liver/tissue.rdata")
load(url("https://www.dropbox.com/s/bq816h74gmh84gu/tissue.rdata?dl=1"))
Loading required package: Seurat
Loading required package: SeuratObject
Loading required package: sp

Attaching package: ‘sp’

The following object is masked from ‘package:IRanges’:

    %over%

‘SeuratObject’ was built under R 4.3.0 but the current version is 4.3.2; it is recomended that you reinstall
‘SeuratObject’ as the ABI for R may have changed
‘SeuratObject’ was built with package ‘Matrix’ 1.6.3 but the current version is 1.6.4; it is recomended that
you reinstall ‘SeuratObject’ as the ABI for ‘Matrix’ may have changed

Attaching package: ‘SeuratObject’

The following object is masked from ‘package:SummarizedExperiment’:

    Assays

The following object is masked from ‘package:GenomicRanges’:

    intersect

The following object is masked from ‘package:GenomeInfoDb’:

    intersect

The following object is masked from ‘package:IRanges’:

    intersect

The following object is masked from ‘package:S4Vectors’:

    intersect

The following object is masked from ‘package:BiocGenerics’:

    intersect

The following object is masked from ‘package:base’:

    intersect

Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio

Attaching package: ‘Seurat’

The following object is masked from ‘package:igraph’:

    components

The following object is masked from ‘package:SummarizedExperiment’:

    Assays
## Convert to SingleCellExperiment
liver_sce <- SingleCellExperiment(assay = list(counts=tissue@raw.data, logcounts=tissue@data),
                                  colData = tissue@meta.data)

liver_sce
class: SingleCellExperiment 
dim: 23498 58358 
metadata(0):
assays(2): counts logcounts
rownames(23498): FO538757.2 AP006222.2 ... CTA-126B4.7 LINC01423
rowData names(0):
colnames(58358): Healthy1_Cd45+_AAACCTGCAGTATCTG Healthy1_Cd45+_AACTGGTTCATGGTCA ...
  Cirrhotic3_Cd45-_TTTGTCATCCAGGGCT Cirrhotic3_Cd45-_TCTGGAAGTCATCCCT
colData names(10): nGene nUMI ... annotation_indepth annotation_lineage
reducedDimNames(0):
mainExpName: NULL
altExpNames(0):

Preprocessing

We use the same number of highly variable genes and principal components used by the authors of the original study.

Feature selection

Select highly variable genes

dec_liver <- modelGeneVar(liver_sce)

fit_liver <- metadata(dec_liver)
plot(fit_liver$mean, fit_liver$var, xlab="Mean of log-expression",
    ylab="Variance of log-expression")


hvgs <- getTopHVGs(dec_liver, n=3000)

Dimensionality reduction

set.seed(42)
liver_sce <- runPCA(liver_sce, subset_row=hvgs, ncomponents=11)
liver_sce <- runUMAP(liver_sce, dimred="PCA", ncomponents=2)
Found more than one class "dist" in cache; using the first, from namespace 'BiocGenerics'
Also defined by ‘spam’
Found more than one class "dist" in cache; using the first, from namespace 'BiocGenerics'
Also defined by ‘spam’
scater::plotUMAP(liver_sce, colour_by="condition", point_alpha=1,  point_size=0.5)

scater::plotUMAP(liver_sce, colour_by="dataset", point_alpha=0.3,  point_size=0.5)

scater::plotUMAP(liver_sce, colour_by="annotation_lineage", point_alpha=0.3,  point_size=0.5, text_by='annotation_lineage')

Notably, this dataset doesn’t appear to display a batch effect

Note: This step is not required. It generates a large file (even larger than raw data) and then reloads it.

# saveRDS(liver_sce, "~/dropbox-vu/temp/milo_data/Ramachandran2019_liver/liver_SCE_20210225.RDS")
# liver_sce <- readRDS(url("https://www.dropbox.com/s/z0aopf616b1urvj/liver_SCE_20210225.RDS?dl=1"))

Differential Abundance (DA) analysis with Milo

We test for differential abundance between healthy and cirrhotic livers. We start by defining neighbourhoods with refined sampling on the KNN graph. We inspect the size of neighbourhoods.

liver_milo <- Milo(liver_sce)

## Build KNN graph
liver_milo <- buildGraph(liver_milo, d = 11, k=30)
Constructing kNN graph with k:30
## Compute neighbourhoods with refined sampling
liver_milo <- makeNhoods(liver_milo, k=30, d=11, prop = 0.05, refined=TRUE)
Checking valid object
Running refined sampling with reduced_dim
plotNhoodSizeHist(liver_milo, bins=150)

Then we make a design matrix for the differential test, assigning samples to biological conditions.

colData(liver_milo)[['sort']] <- str_remove(colData(liver_milo)[['dataset']], ".+_")
colData(liver_milo)[['sort']] <- str_remove(colData(liver_milo)[['sort']], "A|B")

liver_meta <- as_tibble(colData(liver_milo)[,c("dataset","condition", 'sort')])
liver_meta <- distinct(liver_meta) %>%
  mutate(condition=factor(condition, levels=c("Uninjured", "Cirrhotic"))) %>%
  column_to_rownames("dataset")

Now we can count cells in neighbourhoods and perform the DA test.

NOTE: Executing calcNhoodDistance() function takes a long time (>30 min)

liver_milo <- countCells(liver_milo, samples = "dataset", meta.data = data.frame(colData(liver_milo)[,c("dataset","condition",'sort')]) )
Checking meta.data validity
Counting cells in neighbourhoods
liver_milo <- calcNhoodDistance(liver_milo, d=11)
'as(<dgTMatrix>, "dgCMatrix")' is deprecated.
Use 'as(., "CsparseMatrix")' instead.
See help("Deprecated") and help("Matrix-deprecated").
milo_res <- testNhoods(liver_milo, design = ~ condition, design.df = liver_meta[colnames(nhoodCounts(liver_milo)),])
Using TMM normalisation
Performing spatial FDR correction withk-distance weighting
milo_res_sort <- testNhoods(liver_milo, design = ~ sort + condition, design.df = 
                                liver_meta[colnames(nhoodCounts(liver_milo)),])
Using TMM normalisation
Performing spatial FDR correction withk-distance weighting
compare_da_df <- left_join(milo_res_sort, milo_res, by="Nhood", suffix=c("_sort", "_nosort")) %>%
  {annotateNhoods(liver_milo, ., 'annotation_lineage')} 

compare_da_df %>%
  ggplot(aes(-log10(SpatialFDR_sort), -log10(SpatialFDR_nosort))) +
  geom_point(size=0.8) +
  geom_point(data=. %>% filter(annotation_lineage=="Endothelia"), color="red")

plot(milo_res_sort$SpatialFDR, milo_res$SpatialFDR)

Exploration of Milo DA results

We can start by looking at some basic stats

pval_hist <- milo_res %>%
  ggplot(aes(PValue)) +
  geom_histogram(bins=50) +
  theme_bw(base_size=14)

volcano <-
  milo_res %>%
  ggplot(aes(logFC, -log10(SpatialFDR))) +
  geom_point(size=0.4, alpha=0.2) +
  geom_hline(yintercept = -log10(0.1)) +
  xlab("log-Fold Change") +
  theme_bw(base_size=14)

pval_hist + volcano

The distribution of P-values looks sensible and from the volcano plot we can see that we have identified some DA neighbourhoods at 10% FDR.

We can visualize DA neighbourhoods building an abstracted graph

liver_milo <- buildNhoodGraph(liver_milo)
plotNhoodGraphDA(liver_milo, milo_res, alpha = 0.1, size_range=c(2,6))

Note: This step is not required. It generates a large file (even larger than raw data) and then reloads it.

## Save milo object and results
# saveRDS(liver_milo,"~/dropbox-vu/temp/milo_data/Ramachandran2019_liver/liver_milo_20210225.RDS")
# write_csv(milo_res,"~/dropbox-vu/temp/milo_data/Ramachandran2019_liver/liver_results_20210225.csv")
# liver_milo <- readRDS("~/liver_milo_20201008.RDS")
liver_milo <- readRDS(url("https://www.dropbox.com/s/xdp07789c5hoen3/liver_milo_20210225.RDS?dl=1"))
# milo_res <- read_csv("/nfs/team205/ed6/data/Ramachandran2019_liver/liver_results_20201008.csv")
milo_res <- read_csv(url("https://www.dropbox.com/s/i1l3aep1py5wirf/liver_results_20210225.csv?dl=1"))

Making figures for the manuscript


colourCount = length(unique(liver_milo$annotation_lineage))
getPalette = colorRampPalette(brewer.pal(9, "Set2"))
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
umap_df <- data.frame(reducedDim(liver_milo, "UMAP"))
colnames(umap_df) <- c("UMAP_1", "UMAP_2")

umap1 <- cbind(umap_df, annotation_lineage=liver_milo$annotation_lineage) %>%
  ggplot(aes(UMAP_1, UMAP_2, color=as.character(annotation_lineage))) +
  geom_point_rast(size=0.1, alpha=0.5, raster.dpi = 800) +
  ggrepel::geom_text_repel(data = . %>%
              group_by(annotation_lineage) %>%
              summarise(UMAP_1=mean(UMAP_1), UMAP_2=mean(UMAP_2)),
            aes(label=annotation_lineage), color="black", size=6
            ) +
  scale_color_manual(values=getPalette(colourCount)) +
  guides(color="none") +
  xlab("UMAP1") + ylab("UMAP2") +
  coord_fixed() +
  theme_classic(base_size = 22) +
  theme(axis.text = element_blank(), axis.ticks = element_blank())

fn_liver_umap1 <- file.path(OUTPUT_DIR,"/liver_v2/liver_umap1_new.pdf")

if(OVERWRITE | !file.exists(fn_liver_umap1)) {
  ggsave(fn_liver_umap1, height = 7, width = 8)
} else {
  message(cat(fn_liver_umap1,"exists and will not be overwritten\n"))
}
~/dropbox-vu/temp/milo_output//liver_v2/liver_umap1_new.pdf exists and will not be overwritten
umap2 <-
  cbind(umap_df, condition=as.character(liver_milo$condition)) %>%
  ggplot(aes(UMAP_1, UMAP_2, color=condition)) +
  geom_point_rast(size=0.1, alpha=0.5, raster.dpi = 800) +
  scale_color_brewer(palette="Set1", name='') +
  xlab("UMAP1") + ylab("UMAP2") +
  coord_fixed() +
  guides(color='none') +
  facet_wrap(condition~., ncol=1) +
  theme_nothing(font_size = 22) +
  theme(axis.text = element_blank(), axis.ticks = element_blank(), legend.position=c(0.9,0.9),
        strip.background = element_rect(color=NA), strip.text = element_text(size=22))


fn_liver_umap2 <- file.path(OUTPUT_DIR,"/liver_v2/liver_umap2_new.pdf")

if(OVERWRITE | !file.exists(fn_liver_umap1)) {
  ggsave(fn_liver_umap2, height = 7, width = 8)
} else {
  message(cat(fn_liver_umap2,"exists and will not be overwritten\n"))
}
~/dropbox-vu/temp/milo_output//liver_v2/liver_umap2_new.pdf exists and will not be overwritten
nh_graph_pl <- plotNhoodGraphDA(liver_milo, milo_res, alpha = 0.1, size_range=c(1,4)) +
  theme(legend.text = element_text(size=20), legend.title = element_text(size=22)) +
  coord_fixed()

fn_nh_graph <- file.path(OUTPUT_DIR,"/liver_v2/liver_graph_new.pdf")

if(OVERWRITE | !file.exists(fn_nh_graph)) {
  ggsave(fn_nh_graph, height = 7, width = 8)
} else {
  message(cat(fn_nh_graph,"exists and will not be overwritten\n"))
}
~/dropbox-vu/temp/milo_output//liver_v2/liver_graph_new.pdf exists and will not be overwritten
fig4_top <- (umap1 | umap2 | nh_graph_pl) +
  plot_layout(widths = c(3,1,3))

fig4_top

Explore DA neighbourhoods by cell type

Next, we can check the cell types where we observe most differences between healthy and cirrhotic cells, by taking the most frequent cell type in each neighbourhood.

milo_res <- milo_res[,!str_detect(colnames(milo_res), "annotation_lineage")]

# Add annotation of most frequent cell type per nhood to milo results table
milo_res <- annotateNhoods(liver_milo, milo_res, "annotation_indepth")
anno_df <- data.frame(liver_milo@colData) %>%
  distinct(annotation_lineage, annotation_indepth)
milo_res <- left_join(milo_res, anno_df, by="annotation_indepth")

We first check that neighbourhoods are sufficiently homogeneous

frac_hist <- ggplot(milo_res, aes(annotation_indepth_fraction)) +
  geom_histogram(bins=30) +
  xlab("Fraction of cells in \nmost abundant cluster") +
  ylab("# neighbourhoods") +
  theme_bw(base_size=14)

frac_hist

Filter nhoods with homogeneous composition

milo_res$annotation_indepth[milo_res$annotation_indepth_fraction < 0.6] <- NA
milo_res$annotation_lineage[milo_res$annotation_indepth_fraction < 0.6] <- NA

We can recover all the clusters where DA was detected in the original paper

group.by = "annotation_indepth"
paper_DA <- list(cirrhotic=c("MPs (4)","MPs (5)",
                             "Endothelia (6)", "Endothelia (7)",
                             "Mes (3)",
                             "Tcells (2)",
                             "Myofibroblasts"
                             ),
                 healthy=c("MPs (7)",
                           "Endothelia (1)",
                           "Tcells (1)", "Tcells (3)","Tcells (1)",
                           "ILCs (1)"
                           )
                 )

expDA_df <- bind_rows(
  data.frame(annotation_indepth = paper_DA[["cirrhotic"]], pred_DA="cirrhotic"),
  data.frame(annotation_indepth = paper_DA[["healthy"]], pred_DA="healthy")
  )

pl1 <- milo_res %>%
  left_join(expDA_df) %>%
  mutate(is_signif = ifelse(SpatialFDR < 0.1, 1, 0)) %>%
  mutate(logFC_color = ifelse(is_signif==1, logFC, NA)) %>%
  arrange(annotation_lineage) %>%
  mutate(Nhood=factor(Nhood, levels=unique(Nhood))) %>%
  filter(!is.na(annotation_lineage)) %>%
  ggplot(aes(annotation_indepth, logFC, color=logFC_color)) +
  scale_color_gradient2() +
  guides(color="none") +
  xlab(group.by) + ylab("Log Fold Change") +
  ggbeeswarm::geom_quasirandom(alpha=1) +
  coord_flip() +
  facet_grid(annotation_lineage~., scales="free", space="free") +
  theme_bw(base_size=22) +
  theme(strip.text.y =  element_text(angle=0),
        axis.title.y = element_blank(), axis.text.y = element_blank(), axis.ticks.y = element_blank(),
        )

pl2 <- milo_res %>%
  left_join(expDA_df) %>%
  # dplyr::filter(!is.na(pred_DA)) %>%
  group_by(annotation_indepth) %>%
  summarise(pred_DA=dplyr::first(pred_DA), annotation_lineage=dplyr::first(annotation_lineage)) %>%
  mutate(end=ifelse(pred_DA=="healthy", 0, 1),
         start=ifelse(pred_DA=="healthy", 1, 0)) %>%
  filter(!is.na(annotation_lineage)) %>%
  ggplot(aes(annotation_indepth, start, xend = annotation_indepth, yend = end, color=pred_DA)) +
  geom_segment(size=1,arrow=arrow(length = unit(0.1, "npc"), type="closed")) +
  coord_flip() +
  xlab("annotation") +
  facet_grid(annotation_lineage~.,
    # annotation_lineage~"Ramachandran et al.\nDA predictions",
             scales="free", space="free") +
  # guides(color="none") +
  scale_color_brewer(palette="Set1", direction = -1,
                     labels=c("enriched in cirrhotic", "enriched in healthy"),
                     na.translate = F,
                     name="Ramachandran et al.\nDA predictions") +
  guides(color=guide_legend(ncol = 1)) +
  theme_bw(base_size=22) +
  ylim(-0.1,1.1) +
  theme(strip.text.y = element_blank(),strip.text.x = element_text(angle=90),
        plot.margin = unit(c(0,0,0,0), "cm"), panel.grid = element_blank(),
        axis.title.x = element_blank(), axis.text.x = element_blank(), axis.ticks.x = element_blank(),
        legend.position = "bottom")

fig4_bleft <- (pl2 + pl1 +
  plot_layout(widths=c(1,10), guides = "collect") & theme(legend.position = 'top', legend.justification = 0))

fn_liver_DAcomp <- file.path(OUTPUT_DIR,"/liver_v2/liver_DAcomparison.pdf")

if(OVERWRITE | !file.exists(fn_liver_DAcomp)) {
  ggsave(fn_liver_DAcomp, height = 13, width = 8)
} else {
  message(cat(fn_liver_DAcomp,"exists and will not be overwritten\n"))
}
~/dropbox-vu/temp/milo_output//liver_v2/liver_DAcomparison.pdf exists and will not be overwritten
# ggsave("~/mount/gdrive/milo/Figures/liver_v2/liver_DAcomparison.pdf", width=8, height = 13)
# ggsave("~/dropbox-vu/temp/milo_output/liver_v2/liver_DAcomparison.pdf", width=8, height = 13)
fig4_bleft

Close-up on Endothelial lineage

endo_milo <- scater::runUMAP(liver_milo[,liver_milo$annotation_lineage=="Endothelia"],  dimred='PCA')
Found more than one class "dist" in cache; using the first, from namespace 'BiocGenerics'
Also defined by ‘spam’
Found more than one class "dist" in cache; using the first, from namespace 'BiocGenerics'
Also defined by ‘spam’
plotUMAP(endo_milo, colour_by = "annotation_indepth")

umap_df <- data.frame(reducedDim(endo_milo, "UMAP"))
colnames(umap_df) <- c("UMAP_1", "UMAP_2")

endo_umap <- cbind(umap_df, condition=endo_milo$condition) %>%
   ggplot(aes(UMAP_1, UMAP_2, color=condition)) +
  geom_point(size=0.3, alpha=0.5) +
  scale_color_brewer(palette="Set1", name='') +
  xlab("UMAP1") + ylab("UMAP2") +
  coord_fixed() +
  guides(color="none") +
  facet_wrap(condition~., ncol=1) +
  theme_nothing() +
  theme(axis.text = element_blank(), axis.ticks = element_blank(), legend.position=c(0.9,0.9),
        strip.background = element_rect(color=NA), strip.text = element_text(size=22))
liver_milo2 <- liver_milo
subset.nhoods <- str_detect(milo_res$annotation_indepth, "Endo")
reducedDim(liver_milo2, "UMAP")[colnames(endo_milo),] <- reducedDim(endo_milo, "UMAP") 

endo_gr <-
  plotNhoodGraphDA(
  liver_milo2, milo_res,
  subset.nhoods = which(milo_res$annotation_lineage == "Endothelia"), 
  size_range=c(1,4),
  # ) =)[1:(length()-1)], 
  alpha = 0.1
  )  +
   theme(legend.text = element_text(size=20), legend.title = element_text(size=22))
  
fig4_bright1 <- ((endo_umap + endo_gr ) + 
  plot_layout(widths = c(1,2), 
                guides = "collect"
                )) 
fig4_bright1

Can’t get code below to run

Unlcear what the problem is; data structure?

# # nh_graph <- nhoodGraph(liver_milo)[subset.nhoods,subset.nhoods]
# 
# nh_graph <- nhoodGraph(liver_milo)[na.omit(subset.nhoods)]
# 
# nh_graph <- graph_from_adjacency_matrix(nh_graph)
# 
# col_vals <- colData(liver_milo)[as.numeric(vertex_attr(nh_graph)$name), colour_by]
# V(nh_graph)$colour_by <- ifelse(milo_res[subset.nhoods,"SpatialFDR"] > 0.1, 0, milo_res[subset.nhoods,"logFC"])
# ggraph(simplify(nh_graph)) +
#       geom_edge_link0(edge_colour = "grey66", edge_alpha=0.2)   +
#       geom_node_point(aes(fill = colour_by), shape=21, size=2) +
#   scale_fill_gradient2()

Close-up on Cholangiocytes

chol_milo <- scater::runUMAP(liver_milo[,liver_milo$annotation_lineage=="Cholangiocytes"],  dimred='PCA')
Found more than one class "dist" in cache; using the first, from namespace 'BiocGenerics'
Also defined by ‘spam’
Found more than one class "dist" in cache; using the first, from namespace 'BiocGenerics'
Also defined by ‘spam’
plotUMAP(chol_milo, colour_by = "annotation_indepth")


plotUMAP(chol_milo, colour_by = "percent.mito")

Filter out cells that show contamination from immune cells (expression of immune markers)

keep <- logcounts(chol_milo)["CD19",] == 0 | logcounts(chol_milo)["MS4A1",] == 0
chol_milo <- chol_milo[,keep]
chol_milo <- scater::runUMAP(chol_milo,  dimred='PCA')
Found more than one class "dist" in cache; using the first, from namespace 'BiocGenerics'
Also defined by ‘spam’
Found more than one class "dist" in cache; using the first, from namespace 'BiocGenerics'
Also defined by ‘spam’
plotUMAP(chol_milo, colour_by = "annotation_indepth")

umap_df <- data.frame(reducedDim(chol_milo, "UMAP"))
colnames(umap_df) <- c("UMAP_1", "UMAP_2")

chol_umap <- cbind(umap_df, condition=chol_milo$condition) %>%
   ggplot(aes(UMAP_1, UMAP_2, color=condition)) +
  geom_point(size=0.3, alpha=0.5) +
  scale_color_brewer(palette="Set1", name='') +
  xlab("UMAP1") + ylab("UMAP2") +
  coord_fixed() +
  guides(color="none") +
  facet_wrap(condition~., ncol=1) +
  theme_nothing() +
  theme(axis.text = element_blank(), axis.ticks = element_blank(), legend.position=c(0.9,0.9),
        strip.background = element_rect(color=NA), strip.text = element_text(size=22))

chol_umap

liver_milo2 <- liver_milo
subset.nhoods <- milo_res$annotation_lineage=="Cholangiocytes"
reducedDim(liver_milo2, "UMAP")[colnames(chol_milo),] <- reducedDim(chol_milo, "UMAP") 

chol_gr <-
  plotNhoodGraphDA(
  liver_milo2, milo_res,
  subset.nhoods = subset.nhoods,
  size_range=c(2,5),
  # ) =)[1:(length()-1)], 
  alpha = 0.1
  )  +
   theme(legend.text = element_text(size=22), legend.title = element_text(size=24))
  
(chol_umap + chol_gr ) + 
  plot_layout(widths = c(1,2), 
                guides = "collect"
                )

# fig4_bright1 +
#   ggsave("~/milo_output/liver_endoGraph.pdf", width=9, height = 5)  

# ggsave("~/dropbox-vu/temp/milo_output/liver_endoGraph.pdf", width=9, height = 5)  

Differential Gene Expression analysis

In a subset of lineages, we want to test for differential expression between neighbourhoods enriched in cirrhotic cells and neighbourhoods enriched.

Add nhood expression to speed-up plotting of heatmaps

liver_milo <- calcNhoodExpression(liver_milo, assay = "logcounts", subset.row = hvgs)
# DRT: stopping run-through here
Should be able to run everything above.

Endothelia

Rebuttal figure showcasing grouping

set.seed(42)
milo_res_endogroups <- groupNhoods(liver_milo, milo_res, max.lfc.delta = 2, overlap = 1)
Found 1385 DA neighbourhoods at FDR 10%
nhoodAdjacency found - using for nhood grouping
p1 <- plotNhoodGroups(liver_milo, milo_res_endogroups, 
                size_range=c(1,3)) 

milo_res_endogroups <- annotateNhoods(liver_milo, milo_res_endogroups, 'annotation_lineage')

p2 <- plotDAbeeswarm(milo_res_endogroups, group.by = 'NhoodGroup') +
  facet_grid(annotation_lineage~., scales="free", space="free")
Converting group_by to factor...
## Plot expression in T cell neighbourhoods
# markers_df <- read_csv("~/mount/gdrive/milo/STable3_Ramachandran.csv")
tcell_marker_genes <- 
  markers_df %>%
  filter(cluster %in% c("Tcell", "ILC")) %>%
  top_n(30, myAUC) %>%
  pull(gene)
Error: object 'markers_df' not found

Group endothelial cells by logFC and DA results

Calculate marker genes between the two groups

Visualize as volcano

Visualize as heatmap

(gene expression values are scaled between 0 and 1 for each gene)

GO term analysis

Cholangiocytes

Calculate marker genes between the two groups

Visualize as volcano

GO term analysis


Assemble figure

Assemble supplementary figure

LS0tCnRpdGxlOiAiTWlsbzogbGl2ZXIgY2lycmhvc2lzIGFuYWx5c2lzIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKLS0tCgojIyBJbnRyb2R1Y3Rpb24KCkluIHRoaXMgbm90ZWJvb2sgd2UgZGVtb25zdHJhdGUgaG93IHRvIHVzZSBNaWxvIHRvIGRldGVjdCBhYmhlcnJhbnQgY2VsbCBzdGF0ZXMgaW4gZGlzZWFzZWQgdGlzc3VlcywgdXNpbmcgYSBkYXRhc2V0IG9mIGhlcGF0aWMgbm9uLXBhcmVuY2h5bWFsIGNlbGxzIGlzb2xhdGVkIGZyb20gNSBoZWFsdGh5IGFuZCA1IGNpcnJob3RpYyBodW1hbiBsaXZlcnMuIFtSYW1hY2hhbmRyYW4gZXQgYWwuIDIwMTldKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvczQxNTg2LTAxOS0xNjMxLTMjU2VjMSkgKEdFTyBhY2Nlc3NpaW9uOiBHU0UxMzYxMDMpLgoKIyMgSW5zdGFsbGF0aW9uCiMjIyMgQWRkZWQgYnkgRFJUIDIwMjEtMTItMDMKIyMjIyBVcGRhdGVkIGJ5IERSVCAyMDIzLTExLTI5CmBgYHtyIEluc3RhbGxhdGlvbn0Kc291cmNlKCJnZXRSZXFkUGtncy5yIikKCnBrZ3MgPC0gYygiU2luZ2xlQ2VsbEV4cGVyaW1lbnQiLCJzY2F0ZXIiLCJzY3JhbiIsIlNldXJhdCIsIm1pbG9SIiwKICAidGlkeXZlcnNlIiwicGF0Y2h3b3JrIiwiaWdyYXBoIiwiZ2dwbG90MiIsCiAgImdncmFzdHIiLCJtc2lnZGJyIiwiY2x1c3RlclByb2ZpbGVyIiwKICAiUkNvbG9yQnJld2VyIiwiY293cGxvdCIsImRldnRvb2xzIikKCmV4dHJhX3BrZ3MgPC0gYygiaXJsYmEiLCJNYXRyaXgiKSAjIE5PVEUgdGhlc2UgbXVzdCBiZSBpbnN0YWxsZWQgZnJvbSBzb3VyY2UgdG8gbWFrZSBjb2RlIHdvcms7IHNlZSBuZXh0IGNvZGUgYmxvY2sKCmludmlzaWJsZShnZXRSZXFkUGtncyhwa2dzKSkKYGBgCgoKIyMjIE5PVEUKQXMgb2YgMjAyMy0xMS0yOSwgdGhlIHJ1blBDQSBmdW5jdGlvbiBpbiBzY2F0ZXIgYnJlYWtzIGR1ZSB0byBjaGFuZ2UgaW4gTWF0cml4IGxpYnJhcnk7IG11c3QgZG93bmdyYWRlIHRvIE1hdHJpeCAxLjYuMQpEb3dubG9hZGVkIHYgMS42LjEgZnJvbSBodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy9zcmMvY29udHJpYi9BcmNoaXZlL01hdHJpeC8gIAoKaW5zdGFsbGluZyBmcm9tIHNvdXJjZSAoY29tcGlsaW5nIG9uIHlvdXIgY29tcHV0ZXIsIHJhdGhlciB0aGFuIGluc3RhbGxpbmcgZnJvbSBhIGJpbmFyeSkgbWF5IGFsc28gYmUgbmVlZGVkLiBPbiBhIE1hYywgdGhpcyByZXF1aXJlcyBnZm9ydHJhbiB0byBiZSBpbnN0YWxsZWQuICBTZWUgW2hlcmVdKGh0dHBzOi8vbWFjLnItcHJvamVjdC5vcmcvdG9vbHMvKSBhbmQgW2xpbmsgdG8gZG93bmxvYWQgZ2ZvcnRyYW4gMTIuMl0oaHR0cHM6Ly9tYWMuci1wcm9qZWN0Lm9yZy90b29scy9nZm9ydHJhbi0xMi4yLXVuaXZlcnNhbC5wa2cpICAKClNlZSB0aGlzIGxpbms6IGh0dHBzOi8vZ2l0aHViLmNvbS9id2xld2lzL2lybGJhL2lzc3Vlcy83MAoKYGBge3J9CiMgaW5zdGFsbC5wYWNrYWdlcygiaXJsYmEiLCB0eXBlID0gInNvdXJjZSIpCiMgaW5zdGFsbC5wYWNrYWdlcygiTWF0cml4IiwgdHlwZSA9ICJzb3VyY2UiKSAjICwgdmVyc2lvbj0iMS42LjEiCmBgYAoKYGBge3J9CmxpYnJhcnkoc2NhdGVyKQpsaWJyYXJ5KHNjcmFuKQpsaWJyYXJ5KG1pbG9SKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShwYXRjaHdvcmspCmxpYnJhcnkoaWdyYXBoKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2dyYXN0cikKbGlicmFyeShtc2lnZGJyKQpsaWJyYXJ5KGNvd3Bsb3QpCmxpYnJhcnkoTWF0cml4KQpsaWJyYXJ5KGlybGJhKQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKbGlicmFyeShTaW5nbGVDZWxsRXhwZXJpbWVudCkKYGBgCiMjIERlZmluZSBzb21lIGdsb2JhbCB2YXJpYWJsZXMKTG9jYXRpb24gdG8gc2F2ZSBvdXRwdXQgZmlsZXMgYW5kIHdoZXRoZXIgdG8gb3ZlcndyaXRlIG91dHB1dCBmaWxlcy4KYGBge3J9Ck9VVFBVVF9ESVIgPC0gIn4vZHJvcGJveC12dS90ZW1wL21pbG9fb3V0cHV0IgpPVkVSV1JJVEUgPC0gRkFMU0UKYGBgCgoKIyMgTG9hZCBkYXRhCgpXZSBkb3dubG9hZGVkIHRoZSBkYXRhc2V0IGFuZCBhbm5vdGF0aW9ucyBzdG9yZWQgaW4gU2V1cmF0IG9iamVjdCBmcm9tIFtoZXJlXShodHRwczovL2RhdGFzaGFyZS5pcy5lZC5hYy51ay9oYW5kbGUvMTAyODMvMzQzMyksIGFzIGluZGljYXRlZCBieSB0aGUgYXV0aG9ycy4KCmBgYHtyfQojIGxvYWQoIi9uZnMvdGVhbTIwNS9lZDYvZGF0YS9SYW1hY2hhbmRyYW4yMDE5X2xpdmVyL3Rpc3N1ZS5yZGF0YSIpCmxvYWQodXJsKCJodHRwczovL3d3dy5kcm9wYm94LmNvbS9zL2JxODE2aDc0Z21oODRndS90aXNzdWUucmRhdGE/ZGw9MSIpKQojIyBDb252ZXJ0IHRvIFNpbmdsZUNlbGxFeHBlcmltZW50CmxpdmVyX3NjZSA8LSBTaW5nbGVDZWxsRXhwZXJpbWVudChhc3NheSA9IGxpc3QoY291bnRzPXRpc3N1ZUByYXcuZGF0YSwgbG9nY291bnRzPXRpc3N1ZUBkYXRhKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbERhdGEgPSB0aXNzdWVAbWV0YS5kYXRhKQoKbGl2ZXJfc2NlCmBgYAoKIyMgUHJlcHJvY2Vzc2luZwoKV2UgdXNlIHRoZSBzYW1lIG51bWJlciBvZiBoaWdobHkgdmFyaWFibGUgZ2VuZXMgYW5kIHByaW5jaXBhbCBjb21wb25lbnRzIHVzZWQgYnkgdGhlIGF1dGhvcnMgb2YgdGhlIG9yaWdpbmFsIHN0dWR5LiAKCiMjIyBGZWF0dXJlIHNlbGVjdGlvbgoKU2VsZWN0IGhpZ2hseSB2YXJpYWJsZSBnZW5lcwoKYGBge3IgU2VsZWN0IGh2Z3N9CmRlY19saXZlciA8LSBtb2RlbEdlbmVWYXIobGl2ZXJfc2NlKQoKZml0X2xpdmVyIDwtIG1ldGFkYXRhKGRlY19saXZlcikKcGxvdChmaXRfbGl2ZXIkbWVhbiwgZml0X2xpdmVyJHZhciwgeGxhYj0iTWVhbiBvZiBsb2ctZXhwcmVzc2lvbiIsCiAgICB5bGFiPSJWYXJpYW5jZSBvZiBsb2ctZXhwcmVzc2lvbiIpCgpodmdzIDwtIGdldFRvcEhWR3MoZGVjX2xpdmVyLCBuPTMwMDApCmBgYAoKIyMjIERpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbgoKYGBge3IsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTh9CnNldC5zZWVkKDQyKQpsaXZlcl9zY2UgPC0gcnVuUENBKGxpdmVyX3NjZSwgc3Vic2V0X3Jvdz1odmdzLCBuY29tcG9uZW50cz0xMSkKbGl2ZXJfc2NlIDwtIHJ1blVNQVAobGl2ZXJfc2NlLCBkaW1yZWQ9IlBDQSIsIG5jb21wb25lbnRzPTIpCgpzY2F0ZXI6OnBsb3RVTUFQKGxpdmVyX3NjZSwgY29sb3VyX2J5PSJjb25kaXRpb24iLCBwb2ludF9hbHBoYT0xLCAgcG9pbnRfc2l6ZT0wLjUpCnNjYXRlcjo6cGxvdFVNQVAobGl2ZXJfc2NlLCBjb2xvdXJfYnk9ImRhdGFzZXQiLCBwb2ludF9hbHBoYT0wLjMsICBwb2ludF9zaXplPTAuNSkKc2NhdGVyOjpwbG90VU1BUChsaXZlcl9zY2UsIGNvbG91cl9ieT0iYW5ub3RhdGlvbl9saW5lYWdlIiwgcG9pbnRfYWxwaGE9MC4zLCAgcG9pbnRfc2l6ZT0wLjUsIHRleHRfYnk9J2Fubm90YXRpb25fbGluZWFnZScpCmBgYAoKTm90YWJseSwgdGhpcyBkYXRhc2V0IGRvZXNuJ3QgYXBwZWFyIHRvIGRpc3BsYXkgYSBiYXRjaCBlZmZlY3QKCiMjIyBOb3RlOiBUaGlzIHN0ZXAgaXMgbm90IHJlcXVpcmVkLiBJdCBnZW5lcmF0ZXMgYSBsYXJnZSBmaWxlIChldmVuIGxhcmdlciB0aGFuIHJhdyBkYXRhKSBhbmQgdGhlbiByZWxvYWRzIGl0LiAKYGBge3Igc2F2ZS1sb2FkIGludGVybWVkIGZpbGUgMSwgZXZhbD1GQUxTRSwgaW5jbHVkZT1UUlVFLCBlY2hvPVRSVUV9CiMgc2F2ZVJEUyhsaXZlcl9zY2UsICJ+L2Ryb3Bib3gtdnUvdGVtcC9taWxvX2RhdGEvUmFtYWNoYW5kcmFuMjAxOV9saXZlci9saXZlcl9TQ0VfMjAyMTAyMjUuUkRTIikKIyBsaXZlcl9zY2UgPC0gcmVhZFJEUyh1cmwoImh0dHBzOi8vd3d3LmRyb3Bib3guY29tL3MvejBhb3BmNjE2YjF1cnZqL2xpdmVyX1NDRV8yMDIxMDIyNS5SRFM/ZGw9MSIpKQpgYGAKCiMjIERpZmZlcmVudGlhbCBBYnVuZGFuY2UgKERBKSBhbmFseXNpcyB3aXRoIE1pbG8KCldlIHRlc3QgZm9yIGRpZmZlcmVudGlhbCBhYnVuZGFuY2UgYmV0d2VlbiBoZWFsdGh5IGFuZCBjaXJyaG90aWMgbGl2ZXJzLiBXZSBzdGFydCBieSBkZWZpbmluZyBuZWlnaGJvdXJob29kcyB3aXRoIHJlZmluZWQgc2FtcGxpbmcgb24gdGhlIEtOTiBncmFwaC4gV2UgaW5zcGVjdCB0aGUgc2l6ZSBvZiBuZWlnaGJvdXJob29kcy4KCmBgYHtyfQpsaXZlcl9taWxvIDwtIE1pbG8obGl2ZXJfc2NlKQoKIyMgQnVpbGQgS05OIGdyYXBoCmxpdmVyX21pbG8gPC0gYnVpbGRHcmFwaChsaXZlcl9taWxvLCBkID0gMTEsIGs9MzApCgojIyBDb21wdXRlIG5laWdoYm91cmhvb2RzIHdpdGggcmVmaW5lZCBzYW1wbGluZwpsaXZlcl9taWxvIDwtIG1ha2VOaG9vZHMobGl2ZXJfbWlsbywgaz0zMCwgZD0xMSwgcHJvcCA9IDAuMDUsIHJlZmluZWQ9VFJVRSkKcGxvdE5ob29kU2l6ZUhpc3QobGl2ZXJfbWlsbywgYmlucz0xNTApCmBgYAoKVGhlbiB3ZSBtYWtlIGEgZGVzaWduIG1hdHJpeCBmb3IgdGhlIGRpZmZlcmVudGlhbCB0ZXN0LCBhc3NpZ25pbmcgc2FtcGxlcyB0byBiaW9sb2dpY2FsIGNvbmRpdGlvbnMuCgpgYGB7cn0KY29sRGF0YShsaXZlcl9taWxvKVtbJ3NvcnQnXV0gPC0gc3RyX3JlbW92ZShjb2xEYXRhKGxpdmVyX21pbG8pW1snZGF0YXNldCddXSwgIi4rXyIpCmNvbERhdGEobGl2ZXJfbWlsbylbWydzb3J0J11dIDwtIHN0cl9yZW1vdmUoY29sRGF0YShsaXZlcl9taWxvKVtbJ3NvcnQnXV0sICJBfEIiKQoKbGl2ZXJfbWV0YSA8LSBhc190aWJibGUoY29sRGF0YShsaXZlcl9taWxvKVssYygiZGF0YXNldCIsImNvbmRpdGlvbiIsICdzb3J0JyldKQpsaXZlcl9tZXRhIDwtIGRpc3RpbmN0KGxpdmVyX21ldGEpICU+JQogIG11dGF0ZShjb25kaXRpb249ZmFjdG9yKGNvbmRpdGlvbiwgbGV2ZWxzPWMoIlVuaW5qdXJlZCIsICJDaXJyaG90aWMiKSkpICU+JQogIGNvbHVtbl90b19yb3duYW1lcygiZGF0YXNldCIpCgpgYGAKCk5vdyB3ZSBjYW4gY291bnQgY2VsbHMgaW4gbmVpZ2hib3VyaG9vZHMgYW5kIHBlcmZvcm0gdGhlIERBIHRlc3QuICAKCk5PVEU6IEV4ZWN1dGluZyBgY2FsY05ob29kRGlzdGFuY2UoKWAgZnVuY3Rpb24gdGFrZXMgYSBsb25nIHRpbWUgKD4zMCBtaW4pCmBgYHtyfQpsaXZlcl9taWxvIDwtIGNvdW50Q2VsbHMobGl2ZXJfbWlsbywgc2FtcGxlcyA9ICJkYXRhc2V0IiwgbWV0YS5kYXRhID0gZGF0YS5mcmFtZShjb2xEYXRhKGxpdmVyX21pbG8pWyxjKCJkYXRhc2V0IiwiY29uZGl0aW9uIiwnc29ydCcpXSkgKQpsaXZlcl9taWxvIDwtIGNhbGNOaG9vZERpc3RhbmNlKGxpdmVyX21pbG8sIGQ9MTEpCm1pbG9fcmVzIDwtIHRlc3ROaG9vZHMobGl2ZXJfbWlsbywgZGVzaWduID0gfiBjb25kaXRpb24sIGRlc2lnbi5kZiA9IGxpdmVyX21ldGFbY29sbmFtZXMobmhvb2RDb3VudHMobGl2ZXJfbWlsbykpLF0pCm1pbG9fcmVzX3NvcnQgPC0gdGVzdE5ob29kcyhsaXZlcl9taWxvLCBkZXNpZ24gPSB+IHNvcnQgKyBjb25kaXRpb24sIGRlc2lnbi5kZiA9IAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpdmVyX21ldGFbY29sbmFtZXMobmhvb2RDb3VudHMobGl2ZXJfbWlsbykpLF0pCmBgYAoKYGBge3J9CmNvbXBhcmVfZGFfZGYgPC0gbGVmdF9qb2luKG1pbG9fcmVzX3NvcnQsIG1pbG9fcmVzLCBieT0iTmhvb2QiLCBzdWZmaXg9YygiX3NvcnQiLCAiX25vc29ydCIpKSAlPiUKICB7YW5ub3RhdGVOaG9vZHMobGl2ZXJfbWlsbywgLiwgJ2Fubm90YXRpb25fbGluZWFnZScpfSAKCmNvbXBhcmVfZGFfZGYgJT4lCiAgZ2dwbG90KGFlcygtbG9nMTAoU3BhdGlhbEZEUl9zb3J0KSwgLWxvZzEwKFNwYXRpYWxGRFJfbm9zb3J0KSkpICsKICBnZW9tX3BvaW50KHNpemU9MC44KSArCiAgZ2VvbV9wb2ludChkYXRhPS4gJT4lIGZpbHRlcihhbm5vdGF0aW9uX2xpbmVhZ2U9PSJFbmRvdGhlbGlhIiksIGNvbG9yPSJyZWQiKQpwbG90KG1pbG9fcmVzX3NvcnQkU3BhdGlhbEZEUiwgbWlsb19yZXMkU3BhdGlhbEZEUikKYGBgCgoKIyMgRXhwbG9yYXRpb24gb2YgTWlsbyBEQSByZXN1bHRzCgpXZSBjYW4gc3RhcnQgYnkgbG9va2luZyBhdCBzb21lIGJhc2ljIHN0YXRzCgpgYGB7cn0KcHZhbF9oaXN0IDwtIG1pbG9fcmVzICU+JQogIGdncGxvdChhZXMoUFZhbHVlKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbnM9NTApICsKICB0aGVtZV9idyhiYXNlX3NpemU9MTQpCgp2b2xjYW5vIDwtCiAgbWlsb19yZXMgJT4lCiAgZ2dwbG90KGFlcyhsb2dGQywgLWxvZzEwKFNwYXRpYWxGRFIpKSkgKwogIGdlb21fcG9pbnQoc2l6ZT0wLjQsIGFscGhhPTAuMikgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IC1sb2cxMCgwLjEpKSArCiAgeGxhYigibG9nLUZvbGQgQ2hhbmdlIikgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZT0xNCkKCnB2YWxfaGlzdCArIHZvbGNhbm8KYGBgCgpUaGUgZGlzdHJpYnV0aW9uIG9mIFAtdmFsdWVzIGxvb2tzIHNlbnNpYmxlIGFuZCBmcm9tIHRoZSB2b2xjYW5vIHBsb3Qgd2UgY2FuIHNlZSB0aGF0IHdlIGhhdmUgaWRlbnRpZmllZCBzb21lIERBIG5laWdoYm91cmhvb2RzIGF0IDEwJSBGRFIuCgpXZSBjYW4gdmlzdWFsaXplIERBIG5laWdoYm91cmhvb2RzIGJ1aWxkaW5nIGFuIGFic3RyYWN0ZWQgZ3JhcGgKCmBgYHtyLCBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9MTB9CmxpdmVyX21pbG8gPC0gYnVpbGROaG9vZEdyYXBoKGxpdmVyX21pbG8pCnBsb3ROaG9vZEdyYXBoREEobGl2ZXJfbWlsbywgbWlsb19yZXMsIGFscGhhID0gMC4xLCBzaXplX3JhbmdlPWMoMiw2KSkKYGBgCgojIyMgTm90ZTogVGhpcyBzdGVwIGlzIG5vdCByZXF1aXJlZC4gSXQgZ2VuZXJhdGVzIGEgbGFyZ2UgZmlsZSAoZXZlbiBsYXJnZXIgdGhhbiByYXcgZGF0YSkgYW5kIHRoZW4gcmVsb2FkcyBpdC4gCmBgYHtyIHNhdmUgaW50ZXJtZWQgZmlsZXMgMiwgZXZhbD1GQUxTRSwgaW5jbHVkZT1UUlVFLCBlY2hvPVRSVUV9CiMjIFNhdmUgbWlsbyBvYmplY3QgYW5kIHJlc3VsdHMKIyBzYXZlUkRTKGxpdmVyX21pbG8sIn4vZHJvcGJveC12dS90ZW1wL21pbG9fZGF0YS9SYW1hY2hhbmRyYW4yMDE5X2xpdmVyL2xpdmVyX21pbG9fMjAyMTAyMjUuUkRTIikKIyB3cml0ZV9jc3YobWlsb19yZXMsIn4vZHJvcGJveC12dS90ZW1wL21pbG9fZGF0YS9SYW1hY2hhbmRyYW4yMDE5X2xpdmVyL2xpdmVyX3Jlc3VsdHNfMjAyMTAyMjUuY3N2IikKCmBgYAoKYGBge3IgbG9hZCBpbnRlcm1lZCBmaWxlcyAyLCBldmFsPUZBTFNFLCBpbmNsdWRlPVRSVUUsIGVjaG89VFJVRX0KIyBsaXZlcl9taWxvIDwtIHJlYWRSRFMoIn4vbGl2ZXJfbWlsb18yMDIwMTAwOC5SRFMiKQpsaXZlcl9taWxvIDwtIHJlYWRSRFModXJsKCJodHRwczovL3d3dy5kcm9wYm94LmNvbS9zL3hkcDA3Nzg5YzVob2VuMy9saXZlcl9taWxvXzIwMjEwMjI1LlJEUz9kbD0xIikpCiMgbWlsb19yZXMgPC0gcmVhZF9jc3YoIi9uZnMvdGVhbTIwNS9lZDYvZGF0YS9SYW1hY2hhbmRyYW4yMDE5X2xpdmVyL2xpdmVyX3Jlc3VsdHNfMjAyMDEwMDguY3N2IikKbWlsb19yZXMgPC0gcmVhZF9jc3YodXJsKCJodHRwczovL3d3dy5kcm9wYm94LmNvbS9zL2kxbDNhZXAxcHk1d2lyZi9saXZlcl9yZXN1bHRzXzIwMjEwMjI1LmNzdj9kbD0xIikpCmBgYAoKCiMjIyBNYWtpbmcgZmlndXJlcyBmb3IgdGhlIG1hbnVzY3JpcHQKCmBgYHtyLCBmaWcud2lkdGg9MTUsIGZpZy5oZWlnaHQ9MTB9Cgpjb2xvdXJDb3VudCA9IGxlbmd0aCh1bmlxdWUobGl2ZXJfbWlsbyRhbm5vdGF0aW9uX2xpbmVhZ2UpKQpnZXRQYWxldHRlID0gY29sb3JSYW1wUGFsZXR0ZShicmV3ZXIucGFsKDksICJTZXQyIikpCgp1bWFwX2RmIDwtIGRhdGEuZnJhbWUocmVkdWNlZERpbShsaXZlcl9taWxvLCAiVU1BUCIpKQpjb2xuYW1lcyh1bWFwX2RmKSA8LSBjKCJVTUFQXzEiLCAiVU1BUF8yIikKCnVtYXAxIDwtIGNiaW5kKHVtYXBfZGYsIGFubm90YXRpb25fbGluZWFnZT1saXZlcl9taWxvJGFubm90YXRpb25fbGluZWFnZSkgJT4lCiAgZ2dwbG90KGFlcyhVTUFQXzEsIFVNQVBfMiwgY29sb3I9YXMuY2hhcmFjdGVyKGFubm90YXRpb25fbGluZWFnZSkpKSArCiAgZ2VvbV9wb2ludF9yYXN0KHNpemU9MC4xLCBhbHBoYT0wLjUsIHJhc3Rlci5kcGkgPSA4MDApICsKICBnZ3JlcGVsOjpnZW9tX3RleHRfcmVwZWwoZGF0YSA9IC4gJT4lCiAgICAgICAgICAgICAgZ3JvdXBfYnkoYW5ub3RhdGlvbl9saW5lYWdlKSAlPiUKICAgICAgICAgICAgICBzdW1tYXJpc2UoVU1BUF8xPW1lYW4oVU1BUF8xKSwgVU1BUF8yPW1lYW4oVU1BUF8yKSksCiAgICAgICAgICAgIGFlcyhsYWJlbD1hbm5vdGF0aW9uX2xpbmVhZ2UpLCBjb2xvcj0iYmxhY2siLCBzaXplPTYKICAgICAgICAgICAgKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1nZXRQYWxldHRlKGNvbG91ckNvdW50KSkgKwogIGd1aWRlcyhjb2xvcj0ibm9uZSIpICsKICB4bGFiKCJVTUFQMSIpICsgeWxhYigiVU1BUDIiKSArCiAgY29vcmRfZml4ZWQoKSArCiAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAyMikgKwogIHRoZW1lKGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSkKCmZuX2xpdmVyX3VtYXAxIDwtIGZpbGUucGF0aChPVVRQVVRfRElSLCIvbGl2ZXJfdjIvbGl2ZXJfdW1hcDFfbmV3LnBkZiIpCgppZihPVkVSV1JJVEUgfCAhZmlsZS5leGlzdHMoZm5fbGl2ZXJfdW1hcDEpKSB7CiAgZ2dzYXZlKGZuX2xpdmVyX3VtYXAxLCBoZWlnaHQgPSA3LCB3aWR0aCA9IDgpCn0gZWxzZSB7CiAgbWVzc2FnZShjYXQoZm5fbGl2ZXJfdW1hcDEsImV4aXN0cyBhbmQgd2lsbCBub3QgYmUgb3ZlcndyaXR0ZW5cbiIpKQp9CgoKdW1hcDIgPC0KICBjYmluZCh1bWFwX2RmLCBjb25kaXRpb249YXMuY2hhcmFjdGVyKGxpdmVyX21pbG8kY29uZGl0aW9uKSkgJT4lCiAgZ2dwbG90KGFlcyhVTUFQXzEsIFVNQVBfMiwgY29sb3I9Y29uZGl0aW9uKSkgKwogIGdlb21fcG9pbnRfcmFzdChzaXplPTAuMSwgYWxwaGE9MC41LCByYXN0ZXIuZHBpID0gODAwKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IlNldDEiLCBuYW1lPScnKSArCiAgeGxhYigiVU1BUDEiKSArIHlsYWIoIlVNQVAyIikgKwogIGNvb3JkX2ZpeGVkKCkgKwogIGd1aWRlcyhjb2xvcj0nbm9uZScpICsKICBmYWNldF93cmFwKGNvbmRpdGlvbn4uLCBuY29sPTEpICsKICB0aGVtZV9ub3RoaW5nKGZvbnRfc2l6ZSA9IDIyKSArCiAgdGhlbWUoYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLCBsZWdlbmQucG9zaXRpb249YygwLjksMC45KSwKICAgICAgICBzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGNvbG9yPU5BKSwgc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTIyKSkKCgpmbl9saXZlcl91bWFwMiA8LSBmaWxlLnBhdGgoT1VUUFVUX0RJUiwiL2xpdmVyX3YyL2xpdmVyX3VtYXAyX25ldy5wZGYiKQoKaWYoT1ZFUldSSVRFIHwgIWZpbGUuZXhpc3RzKGZuX2xpdmVyX3VtYXAxKSkgewogIGdnc2F2ZShmbl9saXZlcl91bWFwMiwgaGVpZ2h0ID0gNywgd2lkdGggPSA4KQp9IGVsc2UgewogIG1lc3NhZ2UoY2F0KGZuX2xpdmVyX3VtYXAyLCJleGlzdHMgYW5kIHdpbGwgbm90IGJlIG92ZXJ3cml0dGVuXG4iKSkKfQoKbmhfZ3JhcGhfcGwgPC0gcGxvdE5ob29kR3JhcGhEQShsaXZlcl9taWxvLCBtaWxvX3JlcywgYWxwaGEgPSAwLjEsIHNpemVfcmFuZ2U9YygxLDQpKSArCiAgdGhlbWUobGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0yMCksIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTIyKSkgKwogIGNvb3JkX2ZpeGVkKCkKCmZuX25oX2dyYXBoIDwtIGZpbGUucGF0aChPVVRQVVRfRElSLCIvbGl2ZXJfdjIvbGl2ZXJfZ3JhcGhfbmV3LnBkZiIpCgppZihPVkVSV1JJVEUgfCAhZmlsZS5leGlzdHMoZm5fbmhfZ3JhcGgpKSB7CiAgZ2dzYXZlKGZuX25oX2dyYXBoLCBoZWlnaHQgPSA3LCB3aWR0aCA9IDgpCn0gZWxzZSB7CiAgbWVzc2FnZShjYXQoZm5fbmhfZ3JhcGgsImV4aXN0cyBhbmQgd2lsbCBub3QgYmUgb3ZlcndyaXR0ZW5cbiIpKQp9CgpgYGAKCgpgYGB7ciAsIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD0xMH0KZmlnNF90b3AgPC0gKHVtYXAxIHwgdW1hcDIgfCBuaF9ncmFwaF9wbCkgKwogIHBsb3RfbGF5b3V0KHdpZHRocyA9IGMoMywxLDMpKQoKZmlnNF90b3AKYGBgCgojIyMgRXhwbG9yZSBEQSBuZWlnaGJvdXJob29kcyBieSBjZWxsIHR5cGUKCk5leHQsIHdlIGNhbiBjaGVjayB0aGUgY2VsbCB0eXBlcyB3aGVyZSB3ZSBvYnNlcnZlIG1vc3QgZGlmZmVyZW5jZXMgYmV0d2VlbiBoZWFsdGh5IGFuZCBjaXJyaG90aWMgY2VsbHMsIGJ5IHRha2luZyB0aGUgbW9zdCBmcmVxdWVudCBjZWxsIHR5cGUgaW4gZWFjaCBuZWlnaGJvdXJob29kLgoKYGBge3IsIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTEwfQptaWxvX3JlcyA8LSBtaWxvX3Jlc1ssIXN0cl9kZXRlY3QoY29sbmFtZXMobWlsb19yZXMpLCAiYW5ub3RhdGlvbl9saW5lYWdlIildCgojIEFkZCBhbm5vdGF0aW9uIG9mIG1vc3QgZnJlcXVlbnQgY2VsbCB0eXBlIHBlciBuaG9vZCB0byBtaWxvIHJlc3VsdHMgdGFibGUKbWlsb19yZXMgPC0gYW5ub3RhdGVOaG9vZHMobGl2ZXJfbWlsbywgbWlsb19yZXMsICJhbm5vdGF0aW9uX2luZGVwdGgiKQphbm5vX2RmIDwtIGRhdGEuZnJhbWUobGl2ZXJfbWlsb0Bjb2xEYXRhKSAlPiUKICBkaXN0aW5jdChhbm5vdGF0aW9uX2xpbmVhZ2UsIGFubm90YXRpb25faW5kZXB0aCkKbWlsb19yZXMgPC0gbGVmdF9qb2luKG1pbG9fcmVzLCBhbm5vX2RmLCBieT0iYW5ub3RhdGlvbl9pbmRlcHRoIikKYGBgCgpXZSBmaXJzdCBjaGVjayB0aGF0IG5laWdoYm91cmhvb2RzIGFyZSBzdWZmaWNpZW50bHkgaG9tb2dlbmVvdXMKCmBgYHtyfQpmcmFjX2hpc3QgPC0gZ2dwbG90KG1pbG9fcmVzLCBhZXMoYW5ub3RhdGlvbl9pbmRlcHRoX2ZyYWN0aW9uKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbnM9MzApICsKICB4bGFiKCJGcmFjdGlvbiBvZiBjZWxscyBpbiBcbm1vc3QgYWJ1bmRhbnQgY2x1c3RlciIpICsKICB5bGFiKCIjIG5laWdoYm91cmhvb2RzIikgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZT0xNCkKCmZyYWNfaGlzdApgYGAKCkZpbHRlciBuaG9vZHMgd2l0aCBob21vZ2VuZW91cyBjb21wb3NpdGlvbgoKYGBge3J9Cm1pbG9fcmVzJGFubm90YXRpb25faW5kZXB0aFttaWxvX3JlcyRhbm5vdGF0aW9uX2luZGVwdGhfZnJhY3Rpb24gPCAwLjZdIDwtIE5BCm1pbG9fcmVzJGFubm90YXRpb25fbGluZWFnZVttaWxvX3JlcyRhbm5vdGF0aW9uX2luZGVwdGhfZnJhY3Rpb24gPCAwLjZdIDwtIE5BCmBgYAoKCldlIGNhbiByZWNvdmVyIGFsbCB0aGUgY2x1c3RlcnMgd2hlcmUgREEgd2FzIGRldGVjdGVkIGluIHRoZSBvcmlnaW5hbCBwYXBlcgoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xMCwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KZ3JvdXAuYnkgPSAiYW5ub3RhdGlvbl9pbmRlcHRoIgpwYXBlcl9EQSA8LSBsaXN0KGNpcnJob3RpYz1jKCJNUHMgKDQpIiwiTVBzICg1KSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkVuZG90aGVsaWEgKDYpIiwgIkVuZG90aGVsaWEgKDcpIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTWVzICgzKSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlRjZWxscyAoMikiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJNeW9maWJyb2JsYXN0cyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgIGhlYWx0aHk9YygiTVBzICg3KSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICJFbmRvdGhlbGlhICgxKSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICJUY2VsbHMgKDEpIiwgIlRjZWxscyAoMykiLCJUY2VsbHMgKDEpIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIklMQ3MgKDEpIgogICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAgKQoKZXhwREFfZGYgPC0gYmluZF9yb3dzKAogIGRhdGEuZnJhbWUoYW5ub3RhdGlvbl9pbmRlcHRoID0gcGFwZXJfREFbWyJjaXJyaG90aWMiXV0sIHByZWRfREE9ImNpcnJob3RpYyIpLAogIGRhdGEuZnJhbWUoYW5ub3RhdGlvbl9pbmRlcHRoID0gcGFwZXJfREFbWyJoZWFsdGh5Il1dLCBwcmVkX0RBPSJoZWFsdGh5IikKICApCgpwbDEgPC0gbWlsb19yZXMgJT4lCiAgbGVmdF9qb2luKGV4cERBX2RmKSAlPiUKICBtdXRhdGUoaXNfc2lnbmlmID0gaWZlbHNlKFNwYXRpYWxGRFIgPCAwLjEsIDEsIDApKSAlPiUKICBtdXRhdGUobG9nRkNfY29sb3IgPSBpZmVsc2UoaXNfc2lnbmlmPT0xLCBsb2dGQywgTkEpKSAlPiUKICBhcnJhbmdlKGFubm90YXRpb25fbGluZWFnZSkgJT4lCiAgbXV0YXRlKE5ob29kPWZhY3RvcihOaG9vZCwgbGV2ZWxzPXVuaXF1ZShOaG9vZCkpKSAlPiUKICBmaWx0ZXIoIWlzLm5hKGFubm90YXRpb25fbGluZWFnZSkpICU+JQogIGdncGxvdChhZXMoYW5ub3RhdGlvbl9pbmRlcHRoLCBsb2dGQywgY29sb3I9bG9nRkNfY29sb3IpKSArCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQyKCkgKwogIGd1aWRlcyhjb2xvcj0ibm9uZSIpICsKICB4bGFiKGdyb3VwLmJ5KSArIHlsYWIoIkxvZyBGb2xkIENoYW5nZSIpICsKICBnZ2JlZXN3YXJtOjpnZW9tX3F1YXNpcmFuZG9tKGFscGhhPTEpICsKICBjb29yZF9mbGlwKCkgKwogIGZhY2V0X2dyaWQoYW5ub3RhdGlvbl9saW5lYWdlfi4sIHNjYWxlcz0iZnJlZSIsIHNwYWNlPSJmcmVlIikgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZT0yMikgKwogIHRoZW1lKHN0cmlwLnRleHQueSA9ICBlbGVtZW50X3RleHQoYW5nbGU9MCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICkKCnBsMiA8LSBtaWxvX3JlcyAlPiUKICBsZWZ0X2pvaW4oZXhwREFfZGYpICU+JQogICMgZHBseXI6OmZpbHRlcighaXMubmEocHJlZF9EQSkpICU+JQogIGdyb3VwX2J5KGFubm90YXRpb25faW5kZXB0aCkgJT4lCiAgc3VtbWFyaXNlKHByZWRfREE9ZHBseXI6OmZpcnN0KHByZWRfREEpLCBhbm5vdGF0aW9uX2xpbmVhZ2U9ZHBseXI6OmZpcnN0KGFubm90YXRpb25fbGluZWFnZSkpICU+JQogIG11dGF0ZShlbmQ9aWZlbHNlKHByZWRfREE9PSJoZWFsdGh5IiwgMCwgMSksCiAgICAgICAgIHN0YXJ0PWlmZWxzZShwcmVkX0RBPT0iaGVhbHRoeSIsIDEsIDApKSAlPiUKICBmaWx0ZXIoIWlzLm5hKGFubm90YXRpb25fbGluZWFnZSkpICU+JQogIGdncGxvdChhZXMoYW5ub3RhdGlvbl9pbmRlcHRoLCBzdGFydCwgeGVuZCA9IGFubm90YXRpb25faW5kZXB0aCwgeWVuZCA9IGVuZCwgY29sb3I9cHJlZF9EQSkpICsKICBnZW9tX3NlZ21lbnQoc2l6ZT0xLGFycm93PWFycm93KGxlbmd0aCA9IHVuaXQoMC4xLCAibnBjIiksIHR5cGU9ImNsb3NlZCIpKSArCiAgY29vcmRfZmxpcCgpICsKICB4bGFiKCJhbm5vdGF0aW9uIikgKwogIGZhY2V0X2dyaWQoYW5ub3RhdGlvbl9saW5lYWdlfi4sCiAgICAjIGFubm90YXRpb25fbGluZWFnZX4iUmFtYWNoYW5kcmFuIGV0IGFsLlxuREEgcHJlZGljdGlvbnMiLAogICAgICAgICAgICAgc2NhbGVzPSJmcmVlIiwgc3BhY2U9ImZyZWUiKSArCiAgIyBndWlkZXMoY29sb3I9Im5vbmUiKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IlNldDEiLCBkaXJlY3Rpb24gPSAtMSwKICAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoImVucmljaGVkIGluIGNpcnJob3RpYyIsICJlbnJpY2hlZCBpbiBoZWFsdGh5IiksCiAgICAgICAgICAgICAgICAgICAgIG5hLnRyYW5zbGF0ZSA9IEYsCiAgICAgICAgICAgICAgICAgICAgIG5hbWU9IlJhbWFjaGFuZHJhbiBldCBhbC5cbkRBIHByZWRpY3Rpb25zIikgKwogIGd1aWRlcyhjb2xvcj1ndWlkZV9sZWdlbmQobmNvbCA9IDEpKSArCiAgdGhlbWVfYncoYmFzZV9zaXplPTIyKSArCiAgeWxpbSgtMC4xLDEuMSkgKwogIHRoZW1lKHN0cmlwLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSxzdHJpcC50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9OTApLAogICAgICAgIHBsb3QubWFyZ2luID0gdW5pdChjKDAsMCwwLDApLCAiY20iKSwgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCgpmaWc0X2JsZWZ0IDwtIChwbDIgKyBwbDEgKwogIHBsb3RfbGF5b3V0KHdpZHRocz1jKDEsMTApLCBndWlkZXMgPSAiY29sbGVjdCIpICYgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ3RvcCcsIGxlZ2VuZC5qdXN0aWZpY2F0aW9uID0gMCkpCgpmbl9saXZlcl9EQWNvbXAgPC0gZmlsZS5wYXRoKE9VVFBVVF9ESVIsIi9saXZlcl92Mi9saXZlcl9EQWNvbXBhcmlzb24ucGRmIikKCmlmKE9WRVJXUklURSB8ICFmaWxlLmV4aXN0cyhmbl9saXZlcl9EQWNvbXApKSB7CiAgZ2dzYXZlKGZuX2xpdmVyX0RBY29tcCwgaGVpZ2h0ID0gMTMsIHdpZHRoID0gOCkKfSBlbHNlIHsKICBtZXNzYWdlKGNhdChmbl9saXZlcl9EQWNvbXAsImV4aXN0cyBhbmQgd2lsbCBub3QgYmUgb3ZlcndyaXR0ZW5cbiIpKQp9CiMgZ2dzYXZlKCJ+L21vdW50L2dkcml2ZS9taWxvL0ZpZ3VyZXMvbGl2ZXJfdjIvbGl2ZXJfREFjb21wYXJpc29uLnBkZiIsIHdpZHRoPTgsIGhlaWdodCA9IDEzKQojIGdnc2F2ZSgifi9kcm9wYm94LXZ1L3RlbXAvbWlsb19vdXRwdXQvbGl2ZXJfdjIvbGl2ZXJfREFjb21wYXJpc29uLnBkZiIsIHdpZHRoPTgsIGhlaWdodCA9IDEzKQpgYGAKCmBgYHtyIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTEzfQpmaWc0X2JsZWZ0CmBgYAoKCiMjIyBDbG9zZS11cCBvbiBFbmRvdGhlbGlhbCBsaW5lYWdlCgpgYGB7cn0KZW5kb19taWxvIDwtIHNjYXRlcjo6cnVuVU1BUChsaXZlcl9taWxvWyxsaXZlcl9taWxvJGFubm90YXRpb25fbGluZWFnZT09IkVuZG90aGVsaWEiXSwgIGRpbXJlZD0nUENBJykKcGxvdFVNQVAoZW5kb19taWxvLCBjb2xvdXJfYnkgPSAiYW5ub3RhdGlvbl9pbmRlcHRoIikKYGBgCgpgYGB7cn0KdW1hcF9kZiA8LSBkYXRhLmZyYW1lKHJlZHVjZWREaW0oZW5kb19taWxvLCAiVU1BUCIpKQpjb2xuYW1lcyh1bWFwX2RmKSA8LSBjKCJVTUFQXzEiLCAiVU1BUF8yIikKCmVuZG9fdW1hcCA8LSBjYmluZCh1bWFwX2RmLCBjb25kaXRpb249ZW5kb19taWxvJGNvbmRpdGlvbikgJT4lCiAgIGdncGxvdChhZXMoVU1BUF8xLCBVTUFQXzIsIGNvbG9yPWNvbmRpdGlvbikpICsKICBnZW9tX3BvaW50KHNpemU9MC4zLCBhbHBoYT0wLjUpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iU2V0MSIsIG5hbWU9JycpICsKICB4bGFiKCJVTUFQMSIpICsgeWxhYigiVU1BUDIiKSArCiAgY29vcmRfZml4ZWQoKSArCiAgZ3VpZGVzKGNvbG9yPSJub25lIikgKwogIGZhY2V0X3dyYXAoY29uZGl0aW9ufi4sIG5jb2w9MSkgKwogIHRoZW1lX25vdGhpbmcoKSArCiAgdGhlbWUoYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLCBsZWdlbmQucG9zaXRpb249YygwLjksMC45KSwKICAgICAgICBzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGNvbG9yPU5BKSwgc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTIyKSkKYGBgCgpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9NCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGl2ZXJfbWlsbzIgPC0gbGl2ZXJfbWlsbwpzdWJzZXQubmhvb2RzIDwtIHN0cl9kZXRlY3QobWlsb19yZXMkYW5ub3RhdGlvbl9pbmRlcHRoLCAiRW5kbyIpCnJlZHVjZWREaW0obGl2ZXJfbWlsbzIsICJVTUFQIilbY29sbmFtZXMoZW5kb19taWxvKSxdIDwtIHJlZHVjZWREaW0oZW5kb19taWxvLCAiVU1BUCIpIAoKZW5kb19nciA8LQogIHBsb3ROaG9vZEdyYXBoREEoCiAgbGl2ZXJfbWlsbzIsIG1pbG9fcmVzLAogIHN1YnNldC5uaG9vZHMgPSB3aGljaChtaWxvX3JlcyRhbm5vdGF0aW9uX2xpbmVhZ2UgPT0gIkVuZG90aGVsaWEiKSwgCiAgc2l6ZV9yYW5nZT1jKDEsNCksCiAgIyApID0pWzE6KGxlbmd0aCgpLTEpXSwgCiAgYWxwaGEgPSAwLjEKICApICArCiAgIHRoZW1lKGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MjApLCBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT0yMikpCiAgCmZpZzRfYnJpZ2h0MSA8LSAoKGVuZG9fdW1hcCArIGVuZG9fZ3IgKSArIAogIHBsb3RfbGF5b3V0KHdpZHRocyA9IGMoMSwyKSwgCiAgICAgICAgICAgICAgICBndWlkZXMgPSAiY29sbGVjdCIKICAgICAgICAgICAgICAgICkpIApmaWc0X2JyaWdodDEKYGBgCgoKIyMjIENhbid0IGdldCBjb2RlIGJlbG93IHRvIHJ1bgpVbmxjZWFyIHdoYXQgdGhlIHByb2JsZW0gaXM7IGRhdGEgc3RydWN0dXJlPwpgYGB7cn0KIyAjIG5oX2dyYXBoIDwtIG5ob29kR3JhcGgobGl2ZXJfbWlsbylbc3Vic2V0Lm5ob29kcyxzdWJzZXQubmhvb2RzXQojIAojIG5oX2dyYXBoIDwtIG5ob29kR3JhcGgobGl2ZXJfbWlsbylbbmEub21pdChzdWJzZXQubmhvb2RzKV0KIyAKIyBuaF9ncmFwaCA8LSBncmFwaF9mcm9tX2FkamFjZW5jeV9tYXRyaXgobmhfZ3JhcGgpCiMgCiMgY29sX3ZhbHMgPC0gY29sRGF0YShsaXZlcl9taWxvKVthcy5udW1lcmljKHZlcnRleF9hdHRyKG5oX2dyYXBoKSRuYW1lKSwgY29sb3VyX2J5XQojIFYobmhfZ3JhcGgpJGNvbG91cl9ieSA8LSBpZmVsc2UobWlsb19yZXNbc3Vic2V0Lm5ob29kcywiU3BhdGlhbEZEUiJdID4gMC4xLCAwLCBtaWxvX3Jlc1tzdWJzZXQubmhvb2RzLCJsb2dGQyJdKQojIGdncmFwaChzaW1wbGlmeShuaF9ncmFwaCkpICsKIyAgICAgICBnZW9tX2VkZ2VfbGluazAoZWRnZV9jb2xvdXIgPSAiZ3JleTY2IiwgZWRnZV9hbHBoYT0wLjIpICAgKwojICAgICAgIGdlb21fbm9kZV9wb2ludChhZXMoZmlsbCA9IGNvbG91cl9ieSksIHNoYXBlPTIxLCBzaXplPTIpICsKIyAgIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKCkKYGBgCgoKIyMjIENsb3NlLXVwIG9uIENob2xhbmdpb2N5dGVzCgpgYGB7cn0KY2hvbF9taWxvIDwtIHNjYXRlcjo6cnVuVU1BUChsaXZlcl9taWxvWyxsaXZlcl9taWxvJGFubm90YXRpb25fbGluZWFnZT09IkNob2xhbmdpb2N5dGVzIl0sICBkaW1yZWQ9J1BDQScpCnBsb3RVTUFQKGNob2xfbWlsbywgY29sb3VyX2J5ID0gImFubm90YXRpb25faW5kZXB0aCIpCgpwbG90VU1BUChjaG9sX21pbG8sIGNvbG91cl9ieSA9ICJwZXJjZW50Lm1pdG8iKQpgYGAKCkZpbHRlciBvdXQgY2VsbHMgdGhhdCBzaG93IGNvbnRhbWluYXRpb24gZnJvbSBpbW11bmUgY2VsbHMgKGV4cHJlc3Npb24gb2YgaW1tdW5lIG1hcmtlcnMpCgpgYGB7cn0Ka2VlcCA8LSBsb2djb3VudHMoY2hvbF9taWxvKVsiQ0QxOSIsXSA9PSAwIHwgbG9nY291bnRzKGNob2xfbWlsbylbIk1TNEExIixdID09IDAKY2hvbF9taWxvIDwtIGNob2xfbWlsb1ssa2VlcF0KY2hvbF9taWxvIDwtIHNjYXRlcjo6cnVuVU1BUChjaG9sX21pbG8sICBkaW1yZWQ9J1BDQScpCgpwbG90VU1BUChjaG9sX21pbG8sIGNvbG91cl9ieSA9ICJhbm5vdGF0aW9uX2luZGVwdGgiKQpgYGAKCmBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9OH0KdW1hcF9kZiA8LSBkYXRhLmZyYW1lKHJlZHVjZWREaW0oY2hvbF9taWxvLCAiVU1BUCIpKQpjb2xuYW1lcyh1bWFwX2RmKSA8LSBjKCJVTUFQXzEiLCAiVU1BUF8yIikKCmNob2xfdW1hcCA8LSBjYmluZCh1bWFwX2RmLCBjb25kaXRpb249Y2hvbF9taWxvJGNvbmRpdGlvbikgJT4lCiAgIGdncGxvdChhZXMoVU1BUF8xLCBVTUFQXzIsIGNvbG9yPWNvbmRpdGlvbikpICsKICBnZW9tX3BvaW50KHNpemU9MC4zLCBhbHBoYT0wLjUpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iU2V0MSIsIG5hbWU9JycpICsKICB4bGFiKCJVTUFQMSIpICsgeWxhYigiVU1BUDIiKSArCiAgY29vcmRfZml4ZWQoKSArCiAgZ3VpZGVzKGNvbG9yPSJub25lIikgKwogIGZhY2V0X3dyYXAoY29uZGl0aW9ufi4sIG5jb2w9MSkgKwogIHRoZW1lX25vdGhpbmcoKSArCiAgdGhlbWUoYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLCBsZWdlbmQucG9zaXRpb249YygwLjksMC45KSwKICAgICAgICBzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGNvbG9yPU5BKSwgc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTIyKSkKCmNob2xfdW1hcApgYGAKCmBgYHtyLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD00LCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQpsaXZlcl9taWxvMiA8LSBsaXZlcl9taWxvCnN1YnNldC5uaG9vZHMgPC0gbWlsb19yZXMkYW5ub3RhdGlvbl9saW5lYWdlPT0iQ2hvbGFuZ2lvY3l0ZXMiCnJlZHVjZWREaW0obGl2ZXJfbWlsbzIsICJVTUFQIilbY29sbmFtZXMoY2hvbF9taWxvKSxdIDwtIHJlZHVjZWREaW0oY2hvbF9taWxvLCAiVU1BUCIpIAoKY2hvbF9nciA8LQogIHBsb3ROaG9vZEdyYXBoREEoCiAgbGl2ZXJfbWlsbzIsIG1pbG9fcmVzLAogIHN1YnNldC5uaG9vZHMgPSBzdWJzZXQubmhvb2RzLAogIHNpemVfcmFuZ2U9YygyLDUpLAogICMgKSA9KVsxOihsZW5ndGgoKS0xKV0sIAogIGFscGhhID0gMC4xCiAgKSAgKwogICB0aGVtZShsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTIyKSwgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MjQpKQogIAooY2hvbF91bWFwICsgY2hvbF9nciApICsgCiAgcGxvdF9sYXlvdXQod2lkdGhzID0gYygxLDIpLCAKICAgICAgICAgICAgICAgIGd1aWRlcyA9ICJjb2xsZWN0IgogICAgICAgICAgICAgICAgKQojIGZpZzRfYnJpZ2h0MSArCiMgICBnZ3NhdmUoIn4vbWlsb19vdXRwdXQvbGl2ZXJfZW5kb0dyYXBoLnBkZiIsIHdpZHRoPTksIGhlaWdodCA9IDUpICAKCiMgZ2dzYXZlKCJ+L2Ryb3Bib3gtdnUvdGVtcC9taWxvX291dHB1dC9saXZlcl9lbmRvR3JhcGgucGRmIiwgd2lkdGg9OSwgaGVpZ2h0ID0gNSkgIApgYGAKCiMjIyBEaWZmZXJlbnRpYWwgR2VuZSBFeHByZXNzaW9uIGFuYWx5c2lzCgpJbiBhIHN1YnNldCBvZiBsaW5lYWdlcywgd2Ugd2FudCB0byB0ZXN0IGZvciBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBiZXR3ZWVuIG5laWdoYm91cmhvb2RzIGVucmljaGVkIGluIGNpcnJob3RpYyBjZWxscyBhbmQgbmVpZ2hib3VyaG9vZHMgZW5yaWNoZWQuICAKCkFkZCBuaG9vZCBleHByZXNzaW9uIHRvIHNwZWVkLXVwIHBsb3R0aW5nIG9mIGhlYXRtYXBzCgpgYGB7cn0KbGl2ZXJfbWlsbyA8LSBjYWxjTmhvb2RFeHByZXNzaW9uKGxpdmVyX21pbG8sIGFzc2F5ID0gImxvZ2NvdW50cyIsIHN1YnNldC5yb3cgPSBodmdzKQpgYGAKCi0tLS0tCiMgRFJUOiBzdG9wcGluZyBydW4tdGhyb3VnaCBoZXJlClNob3VsZCBiZSBhYmxlIHRvIHJ1biBldmVyeXRoaW5nIGFib3ZlLgotLS0tLQoKIyMgRW5kb3RoZWxpYQoKUmVidXR0YWwgZmlndXJlIHNob3djYXNpbmcgZ3JvdXBpbmcKCmBgYHtyLCBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD0xNH0Kc2V0LnNlZWQoNDIpCm1pbG9fcmVzX2VuZG9ncm91cHMgPC0gZ3JvdXBOaG9vZHMobGl2ZXJfbWlsbywgbWlsb19yZXMsIG1heC5sZmMuZGVsdGEgPSAyLCBvdmVybGFwID0gMSkKCnAxIDwtIHBsb3ROaG9vZEdyb3VwcyhsaXZlcl9taWxvLCBtaWxvX3Jlc19lbmRvZ3JvdXBzLCAKICAgICAgICAgICAgICAgIHNpemVfcmFuZ2U9YygxLDMpKSAKCm1pbG9fcmVzX2VuZG9ncm91cHMgPC0gYW5ub3RhdGVOaG9vZHMobGl2ZXJfbWlsbywgbWlsb19yZXNfZW5kb2dyb3VwcywgJ2Fubm90YXRpb25fbGluZWFnZScpCgpwMiA8LSBwbG90REFiZWVzd2FybShtaWxvX3Jlc19lbmRvZ3JvdXBzLCBncm91cC5ieSA9ICdOaG9vZEdyb3VwJykgKwogIGZhY2V0X2dyaWQoYW5ub3RhdGlvbl9saW5lYWdlfi4sIHNjYWxlcz0iZnJlZSIsIHNwYWNlPSJmcmVlIikKCgojIyBQbG90IGV4cHJlc3Npb24gaW4gVCBjZWxsIG5laWdoYm91cmhvb2RzCiMgbWFya2Vyc19kZiA8LSByZWFkX2Nzdigifi9tb3VudC9nZHJpdmUvbWlsby9TVGFibGUzX1JhbWFjaGFuZHJhbi5jc3YiKQp0Y2VsbF9tYXJrZXJfZ2VuZXMgPC0gCiAgbWFya2Vyc19kZiAlPiUKICBmaWx0ZXIoY2x1c3RlciAlaW4lIGMoIlRjZWxsIiwgIklMQyIpKSAlPiUKICB0b3BfbigzMCwgbXlBVUMpICU+JQogIHB1bGwoZ2VuZSkKCnAzIDwtIHBsb3ROaG9vZEV4cHJlc3Npb25Hcm91cHMobGl2ZXJfbWlsbywgbWlsb19yZXNfZW5kb2dyb3VwcywgZmVhdHVyZXMgPSB1bmlxdWUodGNlbGxfbWFya2VyX2dlbmVzKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgc3Vic2V0Lm5ob29kcyA9IG1pbG9fcmVzX2VuZG9ncm91cHMkTmhvb2RHcm91cCAlaW4lIGMoIjMiLCIxMCIsICIxNCIpLAogICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlPVRSVUUsIGNsdXN0ZXJfZmVhdHVyZXMgPSBUUlVFLHNob3dfcm93bmFtZXMgPSBUUlVFCiAgICAgICAgICAgICAgICAgICAgICAgICAgKSArCiAgdGhlbWUoc3RyaXAudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTkwKSkKCmBgYApgYGB7ciwgZmlnLndpZHRoPTE1LCBmaWcuaGVpZ2h0PTEwfQooKChwMSArIHRoZW1lKCkpLyAocDMgKyB0aGVtZShzdHJpcC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MTAsIGFuZ2xlPTQ1KSkpKSArIAogIHBsb3RfbGF5b3V0KGhlaWdodHMgPSBjKDEuMSwxKSwgZ3VpZGVzPSJjb2xsZWN0IgogICAgICAgICAgICAgICl8IAogICgKICAgIHAyICsgdGhlbWVfYncoYmFzZV9zaXplPTE2KSArIHRoZW1lKHN0cmlwLnRleHQueSA9IGVsZW1lbnRfdGV4dChhbmdsZT0wKSkKICAgICkpICsgCiAgcGxvdF9sYXlvdXQod2lkdGhzID0gYygxLjQsIDEpKSArCiAgcGxvdF9hbm5vdGF0aW9uKHRhZ19sZXZlbHMgPSBjKCJBIiwgIkMiLCAiQiIpICkgKwogICMgZ2dzYXZlKCJ+L21vdW50L2dkcml2ZS9taWxvL0ZpZ3VyZXMvbGl2ZXJfdjIvUkZpZ19ncm91cGluZy5wZGYiLCB3aWR0aD0xNSwgaGVpZ2h0ID0gMTIpICsKICAjIGdnc2F2ZSgifi9tb3VudC9nZHJpdmUvbWlsby9GaWd1cmVzL2xpdmVyX3YyL1JGaWdfZ3JvdXBpbmcucG5nIiwgd2lkdGg9MTUsIGhlaWdodCA9IDEyKQpgYGAKCkdyb3VwIGVuZG90aGVsaWFsIGNlbGxzIGJ5IGxvZ0ZDIGFuZCBEQSByZXN1bHRzCgpgYGB7cn0KbWlsb19yZXNfZW5kb2dyb3VwcyRhbm5vdGF0aW9uX2luZGVwdGhbbWlsb19yZXNfZW5kb2dyb3VwcyRhbm5vdGF0aW9uX2luZGVwdGhfZnJhY3Rpb24gPCAwLjZdIDwtIE5BCm1pbG9fcmVzX2VuZG9ncm91cHMkYW5ub3RhdGlvbl9saW5lYWdlW21pbG9fcmVzX2VuZG9ncm91cHMkYW5ub3RhdGlvbl9pbmRlcHRoX2ZyYWN0aW9uIDwgMC42XSA8LSBOQQoKIyMgR3JvdXAgbmVpZ2hib3VyaG9vZHMgYnkgREEgb3V0Y29tZQptaWxvX3Jlc19lbmRvZ3JvdXBzJE5ob29kR3JvdXAgPC0gTkEKbWlsb19yZXNfZW5kb2dyb3VwcyROaG9vZEdyb3VwIDwtIGlmZWxzZSgobWlsb19yZXNfZW5kb2dyb3VwcyRhbm5vdGF0aW9uX2xpbmVhZ2UgPT0gIkVuZG90aGVsaWEiKSAmIChtaWxvX3Jlc19lbmRvZ3JvdXBzJFNwYXRpYWxGRFIgPCAwLjEpICYgKG1pbG9fcmVzX2VuZG9ncm91cHMkbG9nRkMgPCAtMi41KSwgIjU0IiwgbWlsb19yZXNfZW5kb2dyb3VwcyROaG9vZEdyb3VwKQptaWxvX3Jlc19lbmRvZ3JvdXBzJE5ob29kR3JvdXAgPC0gaWZlbHNlKChtaWxvX3Jlc19lbmRvZ3JvdXBzJGFubm90YXRpb25fbGluZWFnZSA9PSAiRW5kb3RoZWxpYSIpICYgKG1pbG9fcmVzX2VuZG9ncm91cHMkU3BhdGlhbEZEUiA8IDAuMSkgJiAobWlsb19yZXNfZW5kb2dyb3VwcyRsb2dGQyA+IDIuNSksICI3MCIsIG1pbG9fcmVzX2VuZG9ncm91cHMkTmhvb2RHcm91cCkKCgpsaXZlcl9taWxvMiA8LSBsaXZlcl9taWxvCnN1YnNldC5uaG9vZHMgPC0gc3RyX2RldGVjdChtaWxvX3JlcyRhbm5vdGF0aW9uX2luZGVwdGgsICJFbmRvIikKcmVkdWNlZERpbShsaXZlcl9taWxvMiwgIlVNQVAiKVtjb2xuYW1lcyhlbmRvX21pbG8pLF0gPC0gcmVkdWNlZERpbShlbmRvX21pbG8sICJVTUFQIikgCmVuZG9fZ3JfZ3JvdXBzIDwtIHBsb3ROaG9vZEdyb3VwcyhsaXZlcl9taWxvMiwgbWlsb19yZXNfZW5kb2dyb3Vwc1ttaWxvX3Jlc19lbmRvZ3JvdXBzJGFubm90YXRpb25fbGluZWFnZT09IkVuZG90aGVsaWEiLF0sIAogICAgICAgICAgICAgICAgc2hvd19ncm91cHMgPSBjKCI1NCIsICI3MCIpLAogICAgICAgICAgICAgICAgc2l6ZV9yYW5nZT1jKDEsNCksCiAgICAgICAgICAgICAgICBzdWJzZXQubmhvb2RzID0gbWlsb19yZXNfZW5kb2dyb3VwcyRhbm5vdGF0aW9uX2xpbmVhZ2U9PSJFbmRvdGhlbGlhIikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCI1NCI9YnJld2VyLnBhbCg0LCAiU3BlY3RyYWwiKVsyXSwgIjcwIj1icmV3ZXIucGFsKDQsICJTcGVjdHJhbCIpWzNdKSwgCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoIjU0Ij0iVW5pbmp1cmVkIGdyb3VwIiwgJzcwJz0gIkNpcnJob3RpYyBncm91cCIpLAogICAgICAgICAgICAgICAgICAgIG5hLnZhbHVlPSJ3aGl0ZSIsCiAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICJOaG9vZCBncm91cCIKICAgICAgICAgICAgICAgICAgICApICsKICB0aGVtZShsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTIwKSwgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MjIpKQpgYGAKCmBgYHtyLCBmaWcud2lkdGg9MTgsIGZpZy5oZWlnaHQ9NX0KZmlnNF9icmlnaHQxIDwtICgoZW5kb191bWFwICsgZW5kb19ncikgKyAKICBwbG90X2xheW91dCh3aWR0aHMgPSBjKDEsMiksIGd1aWRlcz0iY29sbGVjdCIKICAgICAgICAgICAgICAgICkpICYKICB0aGVtZShsZWdlbmQuYm94ID0gImhvcml6b250YWwiLCBsZWdlbmQucG9zaXRpb24gPSAidG9wIiwgbGVnZW5kLmRpcmVjdGlvbiA9ICJ2ZXJ0aWNhbCIpCmZpZzRfYnJpZ2h0MQpgYGAKCgpDYWxjdWxhdGUgbWFya2VyIGdlbmVzIGJldHdlZW4gdGhlIHR3byBncm91cHMKYGBge3J9Cm1pdG9fZ2VuZXMgPC0gc3RyX2RldGVjdChodmdzLCAiXk1ULSIpCm1hcmtlcnNfZGYgPC0gZmluZE5ob29kR3JvdXBNYXJrZXJzKGxpdmVyX21pbG8sIGRhLnJlcyA9IG1pbG9fcmVzX2VuZG9ncm91cHMsIGFzc2F5PSJjb3VudHMiLAogICAgICAgICAgICAgICAgICAgICAgc3Vic2V0Lm5ob29kcyA9IChtaWxvX3Jlc19lbmRvZ3JvdXBzJE5ob29kR3JvdXAgJWluJSBjKCI1NCIsICI3MCIpKSwKICAgICAgICAgICAgICAgICAgICAgIHN1YnNldC5ncm91cHMgPSBjKCI1NCIsICI3MCIpLAogICAgICAgICAgICAgICAgICAgICAgc3Vic2V0LnJvdyA9IGh2Z3NbIW1pdG9fZ2VuZXNdLAogICAgICAgICAgICAgICAgICAgICAgYWdncmVnYXRlLnNhbXBsZXMgPSBUUlVFLCBzYW1wbGVfY29sID0gImRhdGFzZXQiCiAgICAgICAgICAgICAgICAgICAgICApCgptaWxvX3Jlc19lbmRvZ3JvdXBzW21pbG9fcmVzX2VuZG9ncm91cHMkTmhvb2RHcm91cCAlaW4lIGMoIjU0IiwgIjcwIiksXQoKY29sbmFtZXMobWFya2Vyc19kZikgPC0gc3RyX3JlcGxhY2UoY29sbmFtZXMobWFya2Vyc19kZiksICI3MCIsICJjaXJyIikKY29sbmFtZXMobWFya2Vyc19kZikgPC0gc3RyX3JlcGxhY2UoY29sbmFtZXMobWFya2Vyc19kZiksICI1NCIsICJ1bmluaiIpCmBgYAoKIyMjIyBWaXN1YWxpemUgYXMgdm9sY2FubyAKCmBgYHtyLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCmhpZ2hsaWdodF9nZW5lcyA8LSBjKCJQTFZBUCIsICJWV0ExIiwgIkFDS1IxIiwgIklMMzIiLAogICAgICAgICAgICAgICAgICAgICAiQ0xFQzRHIiwgIkNMRUM0TSIsICJGQ04yIiwgIkZDTjMiLAogICAgICAgICAgICAgICAgICAgICAiTEVGMSIpCgptYXJrZXIuZGYgPC0gbWFya2Vyc19kZgptYXJrZXIuZGYgJT4lCiAgbXV0YXRlKGxhYmVsPWlmZWxzZShHZW5lSUQgJWluJSBoaWdobGlnaHRfZ2VuZXMsIEdlbmVJRCwgTkEpKSAlPiUKICBnZ3Bsb3QoYWVzKGxvZ0ZDX2NpcnIsIC1sb2cxMChhZGouUC5WYWxfY2lyciksIAogICAgICAgICAgICAgIyBjb2xvcj1oaWdobGlnaHQKICAgICAgICAgICAgICkpICsgCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsPWxhYmVsKSwgY29sb3I9InJlZCIpICsKICB4bGFiKCJsb2dGQyIpICsgeWxhYigiLSBsb2cxMChBZGouIFAgdmFsdWUpIikgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDIyKQogIApgYGAKCgojIyMjIFZpc3VhbGl6ZSBhcyBoZWF0bWFwIAooZ2VuZSBleHByZXNzaW9uIHZhbHVlcyBhcmUgc2NhbGVkIGJldHdlZW4gMCBhbmQgMSBmb3IgZWFjaCBnZW5lKQoKYGBge3IsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xMiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbWFya2VyX2dlbmVzIDwtIG1hcmtlci5kZiAlPiUKICBkcGx5cjo6ZmlsdGVyKGFkai5QLlZhbF9jaXJyIDwgMC4wNSkgJT4lCiAgcHVsbChHZW5lSUQpCgpmaWc0X2JicmlnaHQgPC0KICBwbG90Tmhvb2RFeHByZXNzaW9uREEobGl2ZXJfbWlsbywgbWlsb19yZXNfZW5kb2dyb3VwcywgYyhtYXJrZXJfZ2VuZXMpLCBjbHVzdGVyX2ZlYXR1cmVzID0gVFJVRSwgYXNzYXkgPSAiY291bnRzIiwKICAgICAgICAgICAgICAgICAgICAgIGFscGhhID0gMC4xLAogICAgICAgICAgICAgICAgICAgICAgc2NhbGVfdG9fMSA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICBzdWJzZXQubmhvb2RzID0gIG1pbG9fcmVzX2VuZG9ncm91cHMkTmhvb2RHcm91cCAlaW4lIGMoIjU0IiwgIjcwIiksCiAgICAgICAgICAgICAgICAgICAgICAjIGdyaWQuc3BhY2UgPSAiZnJlZSIsCiAgICAgICAgICAgICAgICAgICAgICBoaWdobGlnaHRfZmVhdHVyZXMgPSBoaWdobGlnaHRfZ2VuZXMsIHNob3dfcm93bmFtZXMgPSBGQUxTRQogICAgICAgICAgICAgICAgICAgICAgKSArCiAgeWxhYigiREUgZ2VuZXMiKSsKICAjIGZhY2V0X2dyaWQoLn5OaG9vZEdyb3VwLCBzY2FsZXM9ImZyZWUiLCBzcGFjZT0iZnJlZSIpCiAgIHRoZW1lKGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MjIpLCBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT0yNCkpICsKICBwbG90X2xheW91dChoZWlnaHRzID0gYygxLDEwKSkgJiB0aGVtZShsZWdlbmQubWFyZ2luID0gbWFyZ2luKDAsMCwwLDYwKSwgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpCgogIApwbDMgPC0gZmlnNF9iYnJpZ2h0JGRhdGEgJT4lCiAgZ2dwbG90KGFlcyhsb2dGQ19yYW5rLCAxLGZpbGw9bG9nRkMpKSArCiAgZ2VvbV90aWxlKCkgKwogICAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZT0xNikgKwogICAgeWxhYigiIikgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKG5hbWU9IkRBIGxvZ0ZDIikgKwogICAgIyBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YygiNTQiPWJyZXdlci5wYWwoNCwgIlNwZWN0cmFsIilbMl0sICI3MCI9YnJld2VyLnBhbCg0LCAiU3BlY3RyYWwiKVszXSksIAogICAgIyAgICAgICAgICAgICAgICAgbGFiZWxzPWMoIjU0Ij0iVW5pbmp1cmVkIGdyb3VwIiwgJzcwJz0gIkNpcnJob3RpYyBncm91cCIpLAogICAgIyAgICAgICAgICAgICAgICAgbmEudmFsdWU9IndoaXRlIiwKICAgICMgICAgICAgICAgICAgICAgIG5hbWUgPSAiTmhvb2QgZ3JvdXAiCiAgICAjICAgICAgICAgICAgICAgICApICsKICAgIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAuMDEsIDApKSArCiAgICB0aGVtZShheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksIGF4aXMubGluZSA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKQoKZmlnNF9iYnJpZ2h0IDwtIHBsMyAvIGZpZzRfYmJyaWdodCAgKwogIHBsb3RfbGF5b3V0KGhlaWdodHMgPSBjKDEsMjApKQoKZmlnNF9iYnJpZ2h0CmBgYAoKIyMjIEdPIHRlcm0gYW5hbHlzaXMKCmBgYHtyLCBlY2hvPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQojIEJpb2NNYW5hZ2VyOjppbnN0YWxsKCdjbHVzdGVyUHJvZmlsZXInKQojIEJpb2NNYW5hZ2VyOjppbnN0YWxsKCdtc2lnZGJyJykKbGlicmFyeShjbHVzdGVyUHJvZmlsZXIpCmxpYnJhcnkobXNpZ2RicikKCm1fZGYgPC0gbXNpZ2RicihzcGVjaWVzID0gIkhvbW8gc2FwaWVucyIpCm1fdDJnIDwtIG1zaWdkYnIoc3BlY2llcyA9ICJIb21vIHNhcGllbnMiLCBjYXRlZ29yeSA9ICJDNSIsIHN1YmNhdGVnb3J5ID0gIkJQIikgICU+JSAKICBkcGx5cjo6c2VsZWN0KGdzX25hbWUsIGdlbmVfc3ltYm9sKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQptYXJrZXJfZ2VuZXNfdXAgPC0gbWFya2VyLmRmICU+JQogIGRwbHlyOjpmaWx0ZXIoYWRqLlAuVmFsX2NpcnIgPCAwLjA1ICYgbG9nRkNfY2lyciA+IDAuNSkgJT4lCiAgcHVsbChHZW5lSUQpIAoKbWFya2VyX2dlbmVzX2Rvd24gPC0gbWFya2VyLmRmICU+JQogIGRwbHlyOjpmaWx0ZXIoYWRqLlAuVmFsX2NpcnIgPCAwLjA1ICYgbG9nRkNfdW5pbmogPiAwLjUpICU+JQogIHB1bGwoR2VuZUlEKQoKZW1fdXAgPC0gZW5yaWNoZXIobWFya2VyX2dlbmVzX3VwLCBURVJNMkdFTkU9bV90MmcsIHBBZGp1c3RNZXRob2QgPSAiZmRyIiwgCiAgICAgICAgICAgICAgICAgIHVuaXZlcnNlID0gaHZncwogICAgICAgICAgICAgICAgICApCmVtX2Rvd24gPC0gZW5yaWNoZXIobWFya2VyX2dlbmVzX2Rvd24sIFRFUk0yR0VORT1tX3QyZywgcEFkanVzdE1ldGhvZCA9ICJmZHIiLCAKICAgICAgICAgICAgICAgICAgICB1bml2ZXJzZSA9IHJvd25hbWVzKGxpdmVyX21pbG8pCiAgICAgICAgICAgICAgICAgICAgKQoKZW1fcmVzX3VwIDwtIGVtX3VwQHJlc3VsdFtlbV91cEByZXN1bHQkcXZhbHVlIDwgMC4xLF0gJT4lCiAgZHBseXI6OnNlbGVjdCgtIGMoRGVzY3JpcHRpb24pKQplbV9yZXNfZG93biA8LSBlbV9kb3duQHJlc3VsdFtlbV9kb3duQHJlc3VsdCRxdmFsdWUgPCAwLjEsXSAlPiUKICBkcGx5cjo6c2VsZWN0KC0gYyhEZXNjcmlwdGlvbikpCmBgYAoKYGBge3IsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTE1LCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQpnb19lbmRvX3VwIDwtIGVtX3Jlc191cCAlPiUKICB0b3BfbigzMCwgLWxvZzEwKHF2YWx1ZSkpICU+JQogICBtdXRhdGUoSUQ9aWZlbHNlKElEPT0nR09fQU5USUdFTl9QUk9DRVNTSU5HX0FORF9QUkVTRU5UQVRJT05fT0ZfUEVQVElERV9PUl9QT0xZU0FDQ0hBUklERV9BTlRJR0VOX1ZJQV9NSENfQ0xBU1NfSUknLCAiR09fQU5USUdFTl9QUkVTRU5UQVRJT05fVklBX01IQ19DTEFTU19JSSIsIElEKSkgJT4lCiAgbXV0YXRlKFRlcm09ZmFjdG9yKElELCBsZXZlbHM9cmV2KHVuaXF1ZShJRCkpKSkgJT4lCiAgZ2dwbG90KGFlcyhUZXJtLCAtbG9nMTAocXZhbHVlKSkpICsKICBnZW9tX3BvaW50KCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgeGxhYigiR08gQmlvbG9naWNhbCBGdW5jdGlvbiIpICsgeWxhYigiLWxvZzEwKEFkai4gcC12YWx1ZSkiKSArCiAgdGhlbWVfYncoYmFzZV9zaXplPTE4KSArCiAgZ2d0aXRsZSgiQ2lycmhvdGljIGVuZG90aGVsaWEiKQoKZ29fZW5kb19kb3duIDwtIGVtX3Jlc19kb3duICU+JQogIHRvcF9uKDMwLCAtbG9nMTAocXZhbHVlKSkgJT4lCiAgbXV0YXRlKElEPWlmZWxzZShJRD09J0dPX0FOVElHRU5fUFJPQ0VTU0lOR19BTkRfUFJFU0VOVEFUSU9OX09GX1BFUFRJREVfT1JfUE9MWVNBQ0NIQVJJREVfQU5USUdFTl9WSUFfTUhDX0NMQVNTX0lJJywgIkdPX0FOVElHRU5fUFJFU0VOVEFUSU9OX1ZJQV9NSENfQ0xBU1NfSUkiLCBJRCkpICU+JQogIG11dGF0ZShUZXJtPWZhY3RvcihJRCwgbGV2ZWxzPXJldih1bmlxdWUoSUQpKSkpICU+JQogIGdncGxvdChhZXMoVGVybSwgLWxvZzEwKHF2YWx1ZSkpKSArCiAgZ2VvbV9wb2ludCgpICsKICBjb29yZF9mbGlwKCkgKwogIHhsYWIoIkdPIEJpb2xvZ2ljYWwgRnVuY3Rpb24iKSArIHlsYWIoIi1sb2cxMChBZGouIHAtdmFsdWUpIikgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZT0xOCkgKwogIGdndGl0bGUoIlVuaW5qdXJlZCBlbmRvdGhlbGlhIikKCmdvX2VuZG9fdXAKZ29fZW5kb19kb3duCmBgYAoKCmBgYHtyfQplbV9yZXNfdXAKZW1fcmVzX2Rvd24KYGBgCgojIyBDaG9sYW5naW9jeXRlcwoKYGBge3J9CnNldC5zZWVkKDQyKQptaWxvX3Jlc19jaG9sZ3JvdXBzIDwtIGdyb3VwTmhvb2RzKGxpdmVyX21pbG8sIG1pbG9fcmVzLCBtYXgubGZjLmRlbHRhID0gMC41LCBvdmVybGFwID0gMSkKCiMjIEdyb3VwIG5laWdoYm91cmhvb2RzIGJ5IERBIG91dGNvbWUKbWlsb19yZXNfY2hvbGdyb3VwcyROaG9vZEdyb3VwIDwtIE5BCm1pbG9fcmVzX2Nob2xncm91cHMkTmhvb2RHcm91cCA8LSBpZmVsc2UoKG1pbG9fcmVzX2Nob2xncm91cHMkYW5ub3RhdGlvbl9saW5lYWdlID09ICJDaG9sYW5naW9jeXRlcyIpICYgKG1pbG9fcmVzX2Nob2xncm91cHMkU3BhdGlhbEZEUiA8IDAuMSkgJiAobWlsb19yZXNfY2hvbGdyb3VwcyRsb2dGQyA8IC0yLjUpLCAiMzgiLCBtaWxvX3Jlc19jaG9sZ3JvdXBzJE5ob29kR3JvdXApCm1pbG9fcmVzX2Nob2xncm91cHMkTmhvb2RHcm91cCA8LSBpZmVsc2UoKG1pbG9fcmVzX2Nob2xncm91cHMkYW5ub3RhdGlvbl9saW5lYWdlID09ICJDaG9sYW5naW9jeXRlcyIpICYgKG1pbG9fcmVzX2Nob2xncm91cHMkU3BhdGlhbEZEUiA8IDAuMSkgJiAobWlsb19yZXNfY2hvbGdyb3VwcyRsb2dGQyA+IDIuNSksICI0OSIsIG1pbG9fcmVzX2Nob2xncm91cHMkTmhvb2RHcm91cCkKCmxpdmVyX21pbG8yIDwtIGxpdmVyX21pbG8Kc3Vic2V0Lm5ob29kcyA8LSBzdHJfZGV0ZWN0KG1pbG9fcmVzJGFubm90YXRpb25faW5kZXB0aCwgIkNob2wiKQpyZWR1Y2VkRGltKGxpdmVyX21pbG8yLCAiVU1BUCIpW2NvbG5hbWVzKGNob2xfbWlsbyksXSA8LSByZWR1Y2VkRGltKGNob2xfbWlsbywgIlVNQVAiKSAKcGxvdE5ob29kR3JvdXBzKGxpdmVyX21pbG8yLCBtaWxvX3Jlc19jaG9sZ3JvdXBzW21pbG9fcmVzX2Nob2xncm91cHMkYW5ub3RhdGlvbl9saW5lYWdlPT0iQ2hvbGFuZ2lvY3l0ZXMiLF0sIAogICAgICAgICAgICAgICAgc2hvd19ncm91cHMgPSBjKCI0OSIsIjM4IiksCiAgICAgICAgICAgICAgICBzdWJzZXQubmhvb2RzID0gIG1pbG9fcmVzX2Nob2xncm91cHMkYW5ub3RhdGlvbl9saW5lYWdlID09IkNob2xhbmdpb2N5dGVzIikKCmBgYAoKQ2FsY3VsYXRlIG1hcmtlciBnZW5lcyBiZXR3ZWVuIHRoZSB0d28gZ3JvdXBzCmBgYHtyfQojIyBGaWx0ZXIgZ2VuZXMgZXhwcmVzc2VkIGluIGNob2xhbmdpb2N5dGVzCiMgY2hvbF9odmdzIDwtIGh2Z3NbKGNvdW50cyhjaG9sX21pbG8pW2h2Z3MsXSA+IDApICU+JSB7cm93U3VtcyguKS9uY29sKGNob2xfbWlsbyl9ID4gMC4wMV0KbWl0b19nZW5lcyA8LSBzdHJfZGV0ZWN0KGh2Z3MsICJeTVQtIikKCm1hcmtlcnNfZGYgPC0gZmluZE5ob29kR3JvdXBNYXJrZXJzKGxpdmVyX21pbG8sIGRhLnJlcyA9IG1pbG9fcmVzX2Nob2xncm91cHMsIGFzc2F5PSJjb3VudHMiLAogICAgICAgICAgICAgICAgICAgICAgc3Vic2V0Lm5ob29kcyA9IG1pbG9fcmVzX2Nob2xncm91cHMkTmhvb2RHcm91cCAlaW4lYygiNDkiLCIzOCIpLAogICAgICAgICAgICAgICAgICAgICAgc3Vic2V0Lmdyb3VwcyA9IGMoIjQ5IiwiMzgiKSwKICAgICAgICAgICAgICAgICAgICAgIHN1YnNldC5yb3cgPSBodmdzWyFtaXRvX2dlbmVzXSwKICAgICAgICAgICAgICAgICAgICAgIGFnZ3JlZ2F0ZS5zYW1wbGVzID0gVFJVRSwgc2FtcGxlX2NvbCA9ICJkYXRhc2V0IgogICAgICAgICAgICAgICAgICAgICAgKQoKbWFya2Vyc19kZiAKCm1pbG9fcmVzX2Nob2xncm91cHNbbWlsb19yZXNfY2hvbGdyb3VwcyROaG9vZEdyb3VwICVpbiVjKCI0OSIsIjM4IiksXQpgYGAKCiMjIyMgVmlzdWFsaXplIGFzIHZvbGNhbm8gCgpgYGB7ciwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTAsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cm1hcmtlci5kZi5jaG9sIDwtIG1hcmtlcnNfZGYKCnZvbGNhbm9fY2hvbCA8LQogIG1hcmtlci5kZi5jaG9sICU+JQogIG11dGF0ZSh1cD1pZmVsc2UobG9nRkNfNDkgPiAwLCAidXAiLCAiZG93biIpKSAlPiUKICBncm91cF9ieSh1cCkgJT4lCiAgbXV0YXRlKGxhYmVsPWlmZWxzZShyYW5rKGFkai5QLlZhbF80OSkgPCAxNSwgR2VuZUlELCBOQSkpICU+JQogICMgbXV0YXRlKGxhYmVsPWlmZWxzZSgoYWRqLlAuVmFsXzQ5IDwgMC4wNSAmIGxvZ0ZDXzQ5IDwgLTMpIHwgKGFkai5QLlZhbF80OSA8IDAuMDUgJiBsb2dGQ180OSA+IDApLCBHZW5lSUQsIE5BKSkgJT4lCiAgZ2dwbG90KGFlcyhsb2dGQ180OSwgLWxvZzEwKGFkai5QLlZhbF80OSksIAogICAgICAgICAgICAgIyBjb2xvcj1oaWdobGlnaHQKICAgICAgICAgICAgICkpICsgCiAgZ2VvbV9wb2ludChzaXplPTAuOCwgYWxwaGE9MC42KSArCiAgZ2dyZXBlbDo6Z2VvbV90ZXh0X3JlcGVsKGFlcyhsYWJlbD1sYWJlbCksIHNlZ21lbnQuYWxwaGEgPSAwLjIpICsKICB4bGFiKCJsb2dGQyIpICsgeWxhYigiLSBsb2cxMChBZGouIFAgdmFsdWUpIikgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDIyKQoKdm9sY2Fub19jaG9sICAKICAKYGBgCgoKCiMjIyBHTyB0ZXJtIGFuYWx5c2lzCgpgYGB7ciwgZWNobz1GQUxTRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KbWFya2VyX2dlbmVzX2Nob2wgPC0gbWFya2VyLmRmLmNob2wgJT4lCiAgZHBseXI6OmZpbHRlcihhZGouUC5WYWxfNDkgPCAwLjA1ICYgbG9nRkNfNDkgPiAwKSAlPiUKICBwdWxsKEdlbmVJRCkKCmVtX3VwX2Nob2wgPC0gZW5yaWNoZXIobWFya2VyX2dlbmVzX2Nob2wsIFRFUk0yR0VORT1tX3QyZywgcEFkanVzdE1ldGhvZCA9ICJmZHIiLCAKICAgICAgICAgICAgICAgICAgdW5pdmVyc2UgPSByb3duYW1lcyhsaXZlcl9taWxvKQogICAgICAgICAgICAgICAgICApCgplbV9yZXNfdXBfY2hvbCA8LSBlbV91cF9jaG9sQHJlc3VsdFtlbV91cF9jaG9sQHJlc3VsdCRxdmFsdWUgPCAwLjEsXSAlPiUKICBkcGx5cjo6c2VsZWN0KC0gYyhEZXNjcmlwdGlvbikpCmBgYAoKYGBge3IsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTE1LCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQpnb19jaG9sX3VwIDwtIGVtX3Jlc191cF9jaG9sICU+JQogIHRvcF9uKDIwLCAtbG9nMTAocXZhbHVlKSkgJT4lCiAgbXV0YXRlKFRlcm09ZmFjdG9yKElELCBsZXZlbHM9cmV2KHVuaXF1ZShJRCkpKSkgJT4lCiAgZ2dwbG90KGFlcyhUZXJtLCAtbG9nMTAocXZhbHVlKSkpICsKICBnZW9tX3BvaW50KCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgeGxhYigiR08gQmlvbG9naWNhbCBGdW5jdGlvbiIpICsgeWxhYigiLWxvZzEwKEFkai4gcC12YWx1ZSkiKSArCiAgdGhlbWVfYncoYmFzZV9zaXplPTE4KSArCiAgZ2d0aXRsZSgiQ2lycmhvdGljIGNob2xhbmdpb2N5dGVzIikKCmdvX2Nob2xfdXAKYGBgCgpgYGB7cn0KZW1fcmVzX3VwX2Nob2wKYGBgCmBgYHtyLCBlY2hvPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQptYXJrZXJfZ2VuZXNfY2hvbF9kb3duIDwtIG1hcmtlci5kZi5jaG9sICU+JQogIGRwbHlyOjpmaWx0ZXIoYWRqLlAuVmFsXzQ5IDwgMC4wNSAmIGxvZ0ZDXzQ5IDwgMCkgJT4lCiAgcHVsbChHZW5lSUQpCgplbV9kb3duX2Nob2wgPC0gZW5yaWNoZXIobWFya2VyX2dlbmVzX2Nob2xfZG93biwgVEVSTTJHRU5FPW1fdDJnLCBwQWRqdXN0TWV0aG9kID0gImZkciIsIAogICAgICAgICAgICAgICAgICB1bml2ZXJzZSA9IHJvd25hbWVzKGxpdmVyX21pbG8pCiAgICAgICAgICAgICAgICAgICkKCmVtX3Jlc19kb3duX2Nob2wgPC0gZW1fZG93bl9jaG9sQHJlc3VsdFtlbV9kb3duX2Nob2xAcmVzdWx0JHF2YWx1ZSA8IDAuMSxdICU+JQogIGRwbHlyOjpzZWxlY3QoLSBjKERlc2NyaXB0aW9uKSkKYGBgCgoKLS0tCgpBc3NlbWJsZSBmaWd1cmUKYGBge3IsIGZpZy5oZWlnaHQ9MjUsIGZpZy53aWR0aD0xOX0KZmlnNF9ib3R0b20gPC0gKChmaWc0X2JsZWZ0ICsgcGxvdF9sYXlvdXQoKSkgfAogICAgICAoKGZpZzRfYnJpZ2h0MSArIHBsb3RfbGF5b3V0KHRhZ19sZXZlbCA9ICdrZWVwJykpIC8gKGZpZzRfYmJyaWdodCArIHBsb3RfbGF5b3V0KCkpKSArCiAgICAgIHBsb3RfbGF5b3V0KGhlaWdodHMgPSBjKDEsMS42KSkKICAgKSArCiAgcGxvdF9sYXlvdXQod2lkdGhzPWMoMSwxLjQpKQoKKGZpZzRfdG9wIC8gZmlnNF9ib3R0b20pICsKICBwbG90X2xheW91dChoZWlnaHRzPWMoMSwxLjgpKSAgKwogIGdnc2F2ZSgifi9tb3VudC9nZHJpdmUvbWlsby9GaWd1cmVzL2xpdmVyX3YyL2ZpZzRfcmF3LnBkZiIsIGhlaWdodCA9IDI2LCB3aWR0aCA9IDI0LCB1c2VEaW5nYmF0cz1GQUxTRSkgCiAgIyBnZ3NhdmUoIn4vbW91bnQvZ2RyaXZlL21pbG8vRmlndXJlcy9saXZlcl92Mi9maWc0X3Jhdy5wbmciLCBoZWlnaHQgPSAyNiwgd2lkdGggPSAyNCwgdXNlRGluZ2JhdHM9RkFMU0UpCiAgIyBnZ3NhdmUoIn4vbWlsby9tcy9maWd1cmVzL2ZpZ3MvZmlndXJlNS5wZGYiLCBoZWlnaHQgPSAyNiwgd2lkdGggPSAyMiwgdXNlRGluZ2JhdHM9RkFMU0UpCmBgYAoKQXNzZW1ibGUgc3VwcGxlbWVudGFyeSBmaWd1cmUKCmBgYHtyLCBmaWcud2lkdGg9MjUsIGZpZy5oZWlnaHQ9N30KcDEgPC0gcGxvdF9ncmlkKCBnb19lbmRvX3VwKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMSkpLCAKICAgICAgICAgICAgICAgICBnb19lbmRvX2Rvd24rIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAxKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDEpKSwgCiAgICAgICAgICAgICAgICAgbGFiZWxfc2l6ZSA9IDE4LAogICAgICAgICAgICAgICAgIG5jb2w9MSwKICAgICAgICAgICAgICAgICByZWxfaGVpZ2h0cyA9IGMoMiwyKSwKICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIkEiLCAiQiIsIkMiKSkKCnAxCgpjaG9sX2VtYiA8LSAoY2hvbF91bWFwICsgY2hvbF9nciApICsgCiAgcGxvdF9sYXlvdXQod2lkdGhzID0gYygxLDIpLCAKICAgICAgICAgICAgICAgIGd1aWRlcyA9ICJjb2xsZWN0IgogICAgICAgICAgICAgICAgKQoKYGBgCgpgYGB7ciwgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTh9CnBsb3RfZ3JpZCgKICBnb19lbmRvX3VwKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMSkpLCAKICAgICAgICAgICAgICAgICBnb19lbmRvX2Rvd24rIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAxKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDEpKSwgCiAgICAgICAgICAgICAgICAgbGFiZWxfc2l6ZSA9IDE4LAogICAgICAgICAgICAgICAgIG5jb2w9MSwKICAgICAgICAgICAgICAgICByZWxfaGVpZ2h0cyA9IGMoMiwyKSwgcmVsX3dpZHRocyA9IGMoMiwyKSwKICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIkEiLCAiQiIpCiAgKSArCiAgZ2dzYXZlKCJ+L21vdW50L2dkcml2ZS9taWxvL0ZpZ3VyZXMvbGl2ZXJfdjIvc3VwcGxfZmlnX2VuZG8ucGRmIiwgaGVpZ2h0ID0gMTIsIHdpZHRoPTEyKSArCiAgZ2dzYXZlKCJ+L21vdW50L2dkcml2ZS9taWxvL0ZpZ3VyZXMvbGl2ZXJfdjIvc3VwcGxfZmlnX2VuZG8ucG5nIiwgaGVpZ2h0ID0gMTIsIHdpZHRoPTEyKQoKCmBgYApgYGB7ciwgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTh9CnBsb3RfZ3JpZChwbG90X2dyaWQoY2hvbF91bWFwLCBjaG9sX2dyLCB2b2xjYW5vX2Nob2wsIG5yb3c9MSxyZWxfd2lkdGhzID0gYygxLDIsMiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxfc2l6ZSA9IDE4LAogICAgICAgICAgICAgICAgbGFiZWxzID0gYygiQSIsIkIiLCJDIikpLAogICAgICAgICAgICAgICAgZ29fY2hvbF91cCArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAxKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoaGp1c3QgPSAxKSksIAogICAgICAgICAgICAgICAgbmNvbD0xLAogICAgICAgICAgICAgICAgcmVsX2hlaWdodHMgPSBjKDEsMSksCiAgICAgICAgICAgICAgICAgbGFiZWxfc2l6ZSA9IDE4LAogICAgICAgICAgICAgICAgbGFiZWxzPWMoIiIsJ0QnKSkgKwogICBnZ3NhdmUoIn4vbW91bnQvZ2RyaXZlL21pbG8vRmlndXJlcy9saXZlcl92Mi9zdXBwbF9maWc3LnBkZiIsIGhlaWdodCA9IDEzLCB3aWR0aD0xNCkgKwogIGdnc2F2ZSgifi9tb3VudC9nZHJpdmUvbWlsby9GaWd1cmVzL2xpdmVyX3YyL3N1cHBsX2ZpZzcucG5nIiwgaGVpZ2h0ID0gMTMsIHdpZHRoPTE0KSAKYGBgCg==